Test error state of mutation

Hi,

I’m still (!) trying to test the error state of my mutation. I have the following setup:

Component under test:

export function SubmitForm(): React.ReactElement<HTMLFormElement> {
    const [message, setMessage] = useState('');
    const [submitMessage, { loading, error }] = useMutation(MESSAGE_MUTATION);

    return (
        <form
            onSubmit={event => {
                event.preventDefault();

                try {
                    submitMessage({
                        variables: {
                            SendMessageInput: {
                                body: message,
                                threadId: '12a',
                                senderId: 123,
                                recipientId: 321,
                                subject: '',
                            },
                        },
                    });
                    setMessage('');
                } catch {
                    console.log(error)
                }
            }
        }>
            {error && (
                <SecondaryCalloutMessage
                    icon="alertTriangle"
                    status="error"
                    title="Sorry, there was a problem submitting your message"
                >
                    <Paragraph>
                        Please check that all your payment details are correct
                        and try again.
                    </Paragraph>
                </SecondaryCalloutMessage>
            )}
            <fieldset>
                <label htmlFor="input">Compose message</label>
                <input
                    type="text"
                    id="input"
                    value={message}
                    onChange={event => setMessage(event.target.value)}
                />
            </fieldset>
            <button type="submit">Send message {loading && <Spinner />}</button>
        </form>
    );
}

Here’s the test:

it('should render the error state UI', () => {
        jest.spyOn(navigator, 'onLine', 'get').mockReturnValueOnce(false);

        const mockMutation = {
            request: {
                query: MESSAGE_MUTATION,
                variables: {
                    SendMessageInput: {
                        body: 'test',
                        threadId: '12a',
                        senderId: 123,
                        recipientId: 321,
                    },
                },
            },
            result: null,
            error: new Error('Drat'),
        };

        render(
            <ThemeProvider theme={defaultTheme}>
                <MockedProvider mocks={[mockMutation as any]}>
                    <SubmitForm {...mockData} />
                </MockedProvider>
            </ThemeProvider>
        );

        const inputField = screen.getByLabelText(/compose message/i);
        const button = screen.getByText('Send message');

        userEvent.type(inputField, mockNewMessage);
        fireEvent.click(button);

        setTimeout(() => {
            expect(
                screen.getByText(
                    /sorry, there was a problem submitting your message/i
                )
            ).toBeInTheDocument();
        });
    }, 0);

if I don’t use setTimeout() I get a loading spinner in the DOM, but with the test like this, I only get this error:

    Warning: An update to SubmitForm inside a test was not wrapped in act(...).

I’ve also tried using an async test and waitFor() instead of a setTimeout(), however that tears down the environment before I can make the assertion.

I even added a dummy act(() => {}) block which didn’t return the same error, but resulted in the loading state.

Some more assistance would be much appreciated!

One thing you can try is to add a delay to the mockMutation:

const mockMutation = {
  request: { /* ... */ },
  error: new Error("Drat"),
  delay: 20,
};

React act warnings can be squashed by making sure you’re awaiting an act() call until the next state update.

Thanks @brainkim

Adding delay produces the same result (loading spinner), making the test async and using waitFor() results in the folllowing error:

      var evt = document.createEvent('Event');
                         ^

TypeError: Cannot read property 'createEvent' of null

Could there be anything else I’m doing wrong?

We’ve solved this by making the onSubmit async:

            onSubmit={async event => {
                event.preventDefault();

                try {
                    await submitMessage({
                        variables: {
                            SendMessageInput: {
                                body: message,
                                threadId: threadId.toString(),
                                senderId: userId,
                                recipientId:
                                    recipientsNotCurrentUser &&
                                    recipientsNotCurrentUser.userId,
                                subject: '',
                            },
                        },
                    });
                    setMessage('');
                } catch {
                    console.log(error);
                }
            }}

We’re not sure why this was an issue, but without it the error state would never render. If you could help us understand why this is the case, that would be appreciated.

If you ever figure out what makes Jest do that, let me know, because I also experience this bug from time to time (jestjs - Jest Cannot read property 'createEvent' of null - Stack Overflow)

We’ve solved this by making the onSubmit async:

Ah! Perhaps it has something to do with an unhandled promise rejection. Glad to hear you got it working!

1 Like