DEV Community

Ben Read
Ben Read

Posted on

Jest & Apollo Client: testing mutation error states

Apollo's MockProvider is a great tool for testing mutations, however it's a little bit magical, making errors a little difficult to find. If your testing your error state, this might come in handy.

I'm currently building a UI for a messages app, but encountered issues when testing sending new messages. Here's my component:

export function SubmitForm() {
    const [message, setMessage] = useState('');
    const [submitMessage, { loading, error }] = useMutation(MESSAGE_MUTATION);

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

                try {
                    submitMessage({
                        variables: {
                            SendMessageInput: {
                                body: message,
                            },
                        },
                    });
                    setMessage('');
                } catch {
                    console.log(error);
                }
            }}
        >
            {error && (
                <div>Sorry, there was a problem submitting your message</div>
            )}
            <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>
    );
}
Enter fullscreen mode Exit fullscreen mode

I wrote a test suite for this component, all of which worked correctly, until I got to the stage when I was testing the error state:

    it('should render the error state UI', async () => {
        const mockErrorMutation = {
            request: {
                query: MESSAGE_MUTATION,
                variables: {
                    SendMessageInput: {
                        body: 'test',
                    },
                },
            },
            error: new Error('drat'),
        };

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

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

        userEvent.type('test');
        fireEvent.click(button);

        await waitFor(() => {
            expect(
                screen.getByText(
                    /sorry, there was a problem submitting your message/i
                )
            ).toBeInTheDocument();
        });
    });
Enter fullscreen mode Exit fullscreen mode

This test consistently failed, because all we ever got was the loading state. Yet manual testing passed fine.

The solution? Async the submitMessage() function:

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

                try {
                    await submitMessage({
                        variables: {
                            SendMessageInput: {
                                body: message,
                            },
                        },
                    });
                    setMessage('');
                } catch {
                    console.log(error);
                }
            }}
Enter fullscreen mode Exit fullscreen mode

Why does this work? I have no idea. But it seems without making this asynchronous it would always fail.

Ah well. All's well that ends well.

Discussion (0)