c4k
c4k

Reputation: 4426

Test intermediary state in async handler with React and Enzyme

Despite reading the documentation of enzyme, and act, I could not find a response to my use case because the examples only show simple use cases.

I have a React component displaying a button. The onClick handler sets a loading boolean and calls an external API. I want to assert that the component shows the loading indicator when we click on the button.

Here is the component:

export default function MyButton(): ReactElement {
    const [loading, setLoading] = useState<boolean>(false);
    const [data, setData] = useState<any>(null);

    const onClick = async (): Promise<void> => {
        setLoading(true);

        const response = await fetch('/uri');
        setData(await response.json());

        setLoading(false);
    };

    if (loading) {
        return <small>Loading...</small>;
    }

    return (
        <div>
            <button onClick={onClick}>Click Me!</button>

            <div>
                {data}
            </div>
        </div>
    );
}

And here is the test:

test('should display Loading...', async () => {
    window.fetch = () => Promise.resolve({
        json: () => ({
            item1: 'item1',
            item2: 'item2',
        }),
    });

    const component = mount(<MyButton />);

    // Case 1 ✅ => validates the assertion BUT displays the following warning
    component.find('button').simulate('click');
    // Warning: An update to MyButton inside a test was not wrapped in act(...).
    // When testing, code that causes React state updates should be wrapped into act(...):
    // act(() => {
      /* fire events that update state */
    // });
    /* assert on the output */
    // This ensures that you're testing the behavior the user would see in the browser. Learn more at [URL to fb removed because SO does not accept it]

    // Case 2 ❌ => fails the assertion AND displays the warning above
    act(() => {
        component.find('button').simulate('click');
    });

    // Case 3 ❌ => fails the assertion BUT does not display the warning
    await act(async () => {
        component.find('button').simulate('click');
    });

    expect(component.debug()).toContain('Loading...');
});

As you can see, if I get rid of the warning, my test is not satisfying anymore as it waits for the promise to resolve. How can we assert the intermediate state change while using act?

Thanks.

Upvotes: 0

Views: 114

Answers (1)

skyboyer
skyboyer

Reputation: 23705

Just resolve promise manually:

const mockedData = {
  json: () => ({
    item1: 'item1',
    item2: 'item2',
  }),
};
let resolver;
window.fetch = () => new Promise((_resolver) => {
  resolver = _resolver;
});

// ....
await act(async () => {
  component.find('button').simulate('click');
});
expect(component.debug()).toContain('Loading...');
resolver(mockedData);
expect(component.debug()).not.toContain('Loading...');

PS but in sake of readability I'd rather have 2 separate tests: one with new Promise(); that never resolves and another with Promise.resolve(mockedData) that would be resolved automatically

Upvotes: 1

Related Questions