Helps unit test components which use the Apollo Client.
Whilst using the impressive react-apollo
library, I ran into issues while trying to unit test components which used the GraphQL Query
and Mutation
components. The react-apollo
library includes a MockedProvider
component which allows query and mutation results to be mocked, but didn't offer enough control within unit tests. The react-apollo
documentation for testing can be found here.
Specifically, some of the issues I faced were:
- Unable to assert queries/mutations were called with the expected variables
- Unable to assert how many times a specific query was called
- Unable to change the query/mutation result after the
MockProvider
was initialised - Unable to easily control the query/mutation loading state
The mock-apollo-client
library helps with the above issues, by allowing more control within unit tests.
npm install --save-dev mock-apollo-client
Assuming mock-apollo-client
is being used within unit tests and should be installed as a dev dependency.
The examples below use react-apollo
, enzyme
and Jest
, but mock-apollo-client
is standalone and can used with any libraries and test frameworks.
The examples have been adapted from the official react-apollo
testing docs and are written in TypeScript.
Consider the file below, which contains a single GraphQL query and a component which is responsible for rendering the result of the query:
// dog.ts
import * as React from 'react';
import gql from 'graphql-tag';
import { Query } from 'react-apollo';
export const GET_DOG_QUERY = gql`
query getDog($name: String) {
dog(name: $name) {
id
name
breed
}
}
`;
export const Dog = ({ name }) => (
<Query query={GET_DOG_QUERY} variables={{ name }}>
{({ loading, error, data }) => {
if (loading) return 'Loading...';
if (error) return 'Error!';
return (
<p>
{data.dog.name} is a {data.dog.breed}
</p>
);
}}
</Query>
);
To unit test this component using mock-apollo-client
, the test file could look like the following:
// dog.test.ts
import { mount, ReactWrapper } from 'enzyme';
import * as React from 'react';
import { ApolloProvider } from 'react-apollo';
import { createMockClient } from 'mock-apollo-client';
import { GET_DOG_QUERY, Dog } from './dog';
let wrapper: ReactWrapper;
beforeEach(() => {
const mockClient = createMockClient();
mockClient.setRequestHandler(
GET_DOG_QUERY,
() => Promise.resolve({ data: { dog: { id: 1, name: 'Rufus', breed: 'Poodle' } } }));
wrapper = mount(
<ApolloProvider client={mockClient}>
<Dog name="Rufus" />
</ApolloProvider>
);
});
it('renders the dog name and breed', () => {
expect(wrapper.text()).toContain('Rufus is a Poodle');
});
This test file does the following:
- Instantiates a new mock Apollo client
- Calls
setRequestHandler
on the mock Apollo client to set a function to be called when the Apollo client executes the Dog query - Uses the mock and initialises the enzyme wrapper for the unit tests
The method setRequestHandler
is passed a function to call when Apollo client executes a given query and it is called with the variables for that query, so it is easy to assert the component is behaving as expected using a spy framework.
const queryHandler = jest.fn().mockResolvedValue({ data: { dog: { id: 1, name: 'Rufus', breed: 'Poodle' } } });
mockApolloClient.setRequestHandler(GET_DOG_QUERY, requestHandler);
// ....
it('executes the query with the correct variables', () => {
expect(queryHandler).toBeCalledTimes(1);
expect(queryHandler).toBeCalledWith({ name: 'Rufus' });
});
A request handler returns a promise, so testing for loading state just requires that the promise returned is not resolved or rejected.
To simulate a GraphQL network error, the request handler should return a rejected promise. i.e.
mockApolloClient.setRequestHandler(
GET_DOG_QUERY,
() => Promise.reject(new Error('GraphQL Network Error')));
To simulate GraphQL errors, the request handler should return a Promise which resolves with an errors
field. i.e.
mockApolloClient.setRequestHandler(
GET_DOG_QUERY,
() => Promise.resolve({ errors: [{message: 'GraphQL Error'}] }));
Mutations can be tested the same way that queries are, by using setRequestHandler
and specifying a request handler for the mutation query.