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.

Top comments (2)

Collapse
 
peterlitszo profile image
peterlits zo

Thanks, That works!

Collapse
 
peterlitszo profile image
peterlits zo

Could you use better indentation please?

And I know why now: the function that Apollo GraphQL return is an async function. If you do not add the keyword await, it will throw an error in promise and you cannot catch it. But if you add the keyword await, it will wait until it is done (returns or throws an error), and now we can catch it.