25

I'm new to React and confused about all the testing libraries. I got my test code to work but it seems redundant to have to call create() from react-test-renderer in order to use its toMatchSnapshot() and have to call render() from @testing-library/react in order to use its assertions such as getByLabelText().

import {render} from '@testing-library/react';
import {act, create} from 'react-test-renderer';

it('renders a login screen', () => {
    let mockInitialState: AppState = {
        auth: initialAuthState
    };

    let component = <Root initialState={mockInitialState}/>;

    let tree = null;
    act(() => {
        tree = create(component);
    });
    expect(tree).toMatchSnapshot();

    const {getByLabelText, getByText} = render(component);
    expect(getByLabelText(/Email Address.*/));
    expect(getByLabelText(/Password*/));
    expect(getByText('Sign in'));
});

As a newbie, it's hard for me to understand the difference between all these React libraries. But I'm thinking there must be a simpler way.

How can I simplify my test code so I only have to call one thing that renders the component so that I can do snapshot testing and more specific assertions?

Michael Osofsky
  • 11,429
  • 16
  • 68
  • 113

2 Answers2

14

I got the answer from Ziad Saab at Codementor.io:

  • create() allows you test against the virtual DOM (i.e. the "React DOM")

  • render() comes from react testing library and renders your tree but also allows you to have all the get*() assertions. It allows you to test against the DOM.

Here's how the code can be simplified:

it('renders a login screen', () => {
    let mockInitialState: AppState = {
        auth: initialAuthState
    };

    const {container, getByLabelText, getByText} = render(<Root initialState={mockInitialState}/>);
    expect(container.firstChild).toMatchSnapshot();
    expect(getByLabelText(/Email Address.*/));
    expect(getByLabelText(/Password*/));
    expect(getByText('Sign in'));
});

Ziad let me know that there was no reason to have act(), it was something to work around a bug in create(). Now that the code doesn't used create() there is no need for act().

As a result, my snapshot now contains class instead of className because class is what's in the actual HTML DOM whereas className is its equivalent in React's "Virtual DOM".

(Before) Snapshot with create() based on React's Virtual DOM:

className="MuiBox-root MuiBox-root-256"

(After) Snapshot with render() based on HTML DOM:

class="MuiBox-root MuiBox-root-256"
Michael Osofsky
  • 11,429
  • 16
  • 68
  • 113
  • 2
    is there any benefits of testing against DOM instead of Virtual DOM? like any particular situation where virtual DOM testing should be considered? – mehul9595 Jan 14 '21 at 02:54
  • 2
    Generally I would prefer testing the virtual DOM and only test the actual DOM to validate some key requirement that the virtual DOM doesn't allow me to validate. For example, I would write a test against the DOM if a customer reported a bug on a particular browser and I can't reproduce the bug with the virtual DOM. – Michael Osofsky Jan 19 '21 at 18:55
  • 1
    I've noticed that DOM events, such as `onClick` are stripped out when rendering through HTML DOM? So it may be a good idea to use virtual DOM for snapshots? – zanona Mar 28 '21 at 21:49
3

If you're using Create React App then I'd stick with react-testing-library since it comes with it.

Instead of container, you can also use asFragment for snapshot testing.

 const { asFragment } = render(<Root initialState={mockInitialState}/>);
 expect(asFragment()).toMatchSnapshot();

BILL
  • 4,711
  • 10
  • 57
  • 96
nyancodes
  • 41
  • 2
  • `container` was never used and `asFragment` seems undefined. Did you mean to destructure `asFragment` rather than `container`? Also, what's the advantage of using `asFragment` over `container`? Thanks! – ggorlen Mar 18 '23 at 06:36