Testing GraphQL Container Components with React Apollo and Jest

Sean Connolly
6 min readJul 3, 2018

Our team is in the middle of adopting React + GraphQL using React Apollo. One of the things we were excited about was the promise that writing unit tests with React Apollo would be easy. While React Apollo provides some awesome tools for mocking and unit testing the GraphQL side of things, we found that because Apollo makes use of the Higher Order Components and Render Props patterns, unit testing was not as smooth as we thought it would be. At one point, we considered just not testing anything that depended on HOCs or render props because it wasn’t worth the hassle. All of the Mocking is Easy! and Testing is a Breeze! articles out there were not holding true for us and were just like whyyyyyyyyy.

One of those aforementioned articles covers the MockedProvider, which is very cool if you haven’t tried it. Since it allowed us to easily mock the shape of the GraphQL data coming into our components, it seemed like the hard part was done and we could start writing tests unhindered.

But we found out pretty quickly that there were still some hurdles to get over to get to where we wanted. In particular, in scenarios where a Query component or a graphql HOC wraps a deeply nested, complicated tree of components.

Consider this CatContainer component that wraps a react-apollo Query component:

/* CatContainer.js */
import React from 'react';
import { Query } from 'react-apollo';
import GET_CAT_QUERY from './get-cat-query.graphql';
import Cat from './Cat';
const CatContainer = () => (
<Query query={GET_CAT_QUERY}>
{({ loading, error, data }) => {
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error :(</p>;
}
return <Cat cat={data.cat} />;
}}
</Query>
);
export default CatContainer;

The GET_CAT_QUERY is pretty simple:

/* get-cat-query.graphql.js */
import gql from 'graphql-tag';
const GET_CAT_QUERY = gql`
{
cat(id: "123") {
id
name
}
}
`;
export default GET_CAT_QUERY;

And the Cat component, which the CatContainer wraps:

import React from 'react';const Cat = (props) => {
const { cat } = props;
return (
<div>
<p>id: {cat.id}</p>
<p>name: {cat.name}</p>
</div>
);
};
export default Cat;

The Basic Test

This first test shows how you can test everything with Enzyme’s mount. This renders the CatContainer and all of the components in its tree, including the Cat component.

/* __tests__/MountedCat.test.js */
import React from 'react';
import { mount } from 'enzyme';
import wait from 'waait';
import { MockedProvider } from 'react-apollo/test-utils';
import GET_CAT_QUERY from '../get-cat-query.graphql';
import CatContainer from '../CatContainer';
test('Mounted Cat', async () => {
const mocks = [
{
request: { query: GET_CAT_QUERY },
result: {
data: {
cat: {
__typename: 'Cat',
id: '123',
name: 'Cat 123',
},
},
},
},
];
const wrapper = mount((
<MockedProvider mocks={mocks}>
<CatContainer />
</MockedProvider>
));
await wait(0); // Wait a tick to get past the loading state
expect(wrapper.text()).toContain('id: 123');
expect(wrapper.text()).toContain('name: Cat 123');
});

But most likely, you don’t have many components as simple as the Cat component in your project. You probably have components that have many nested components, and likely have dependencies on providers further up the chain (in our case we were using Image components from cloudinary-react, which depend on a CloudinaryContext provider).

Let’s assume the <Cat /> component is deep and complicated just like your components. Fully mounting that component is going to be problematic. You may need to add additional configuration and/or providers to your test just to get it to run.

Enzyme to the Rescue?

Typically you can solve this deep rendering problem by using Enzyme’s shallow function. After all, straight from their docs:

“Shallow rendering is useful to constrain yourself to testing a component as a unit, and to ensure that your tests aren’t indirectly asserting on behavior of child components.”

So in theory we want to useshallow instead of mount:

const wrapper = shallow((
<MockedProvider mocks={mocks}>
<CatContainer />
</MockedProvider>
));

But there is a problem. Given the MockedProvider and the Query component, our component structure actually looks like this:

<MockedProvider>
<ApolloProvider>
<CatContainer>
<Query>
...

We don’t want to shallow render MockedProvider. That wouldn’t execute any of our code. We also don’t want to shallow render CatContainer because that won’t actually execute our GraphQL query.

We found a few workarounds to this problem like enzyme-shallow-until or even calling shallow multiple times explicitly as discussed here.

We tried both of the above and either could not get them to work in our case or didn’t think they would scale to our team well (i.e. adding a new HOC around an existing one would mean you need to update how many times you call shallow if you’re doing it explicitly). We also think we’ll end up using both the Query component and the graphql higher order component depending on the scenario and wanted a pattern that worked regardless of which method we used to query data.

Good ole jest.mock

Jest’s module mocking capability is…let’s face it, kind of quirky especially if you are using es import statements instead of require in your tests. But jest mocks actually fit well in our scenario where we know the thing that we want to stub out, in our case, the Cat component.

/* __tests__/MockedCat.test.js */
import React from 'react';
import { mount } from 'enzyme';
import wait from 'waait';
import { MockedProvider } from 'react-apollo/test-utils';
// Make sure the MockComponent is imported before the CatContainer
import MockComponent from '../../test-support/MockComponent';
import GET_CAT_QUERY from '../get-cat-query.graphql';
import CatContainer from '../CatContainer';
jest.mock('../Cat', () => MockComponent);describe('Mocked Cat', () => {
let mocks;
beforeEach(() => {
mocks = [
{
request: { query: GET_CAT_QUERY },
result: {
data: {
cat: {
__typename: 'Cat',
id: '123',
name: 'Cat 123',
},
},
},
},
];
});
test('<CatContainer />', async () => {
const wrapper = mount((
<MockedProvider mocks={mocks}>
<CatContainer />
</MockedProvider>
));
await wait(0);
expect(wrapper.update().find(MockComponent).prop('cat')).toMatchObject({ id: '123', name: 'Cat 123' });
});
});

Caveat! Make sure the MockComponent is imported before the CatContainer. I generally try to have “the thing under test” as the last import anyway, but in this case it matters because of jest’s magical hoisting capability.

MockComponent can really be anything but ours is simple:

const MockComponent = () => <div />;
export default MockComponent;

HOCs Work Too

If you prefer the React Apollo graphql higher order component or have found cases where you need/want to use those, this pattern works for those too.

Here is the same container component implemented with graphql:

/* CatContainerHOC.js */
import React from 'react';
import { graphql } from 'react-apollo';
import GET_CAT_QUERY from './get-cat-query.graphql';
import Cat from './Cat';
const CatContainerHOC = (props) => {
const { loading, error, data } = props;
if (loading) {
return <p>Loading...</p>;
}
if (error) {
return <p>Error :(</p>;
}
return <Cat cat={data.cat} />;
};
export default graphql(GET_CAT_QUERY)(CatContainerHOC);

And the test setup for these is identical. The first block tests the Query component version and the second tests the graphql version.

/* __tests__/MockedCat.test.js */
import React from 'react';
import { mount } from 'enzyme';
import wait from 'waait';
import { MockedProvider } from 'react-apollo/test-utils';
// Make sure the MockComponent is imported before the CatContainer
import MockComponent from '../../test-support/MockComponent';
import GET_CAT_QUERY from '../get-cat-query.graphql';
import CatContainer from '../CatContainer';
import CatContainerHOC from '../CatContainerHOC';
jest.mock('../Cat', () => MockComponent);describe('Mocked Cat', () => {
let mocks;
beforeEach(() => {
mocks = [
{
request: { query: GET_CAT_QUERY },
result: {
data: {
cat: {
__typename: 'Cat',
id: '123',
name: 'Cat 123',
},
},
},
},
];
});
test('<CatContainer />', async () => {
const wrapper = mount((
<MockedProvider mocks={mocks}>
<CatContainer />
</MockedProvider>
));
await wait(0);
expect(wrapper.update().find(MockComponent).prop('cat')).toMatchObject({ id: '123', name: 'Cat 123' });
});
test('<CatContainerHOC />', async () => {
const wrapper = mount((
<MockedProvider mocks={mocks}>
<CatContainerHOC />
</MockedProvider>
));
await wait(0);
expect(wrapper.update().find(MockComponent).prop('cat')).toMatchObject({ id: '123', name: 'Cat 123' });
});
});

If you are looking for a testing pattern that helps you get around the render props / HOC problem, give Jest mocks a try. Let me know how it goes for you or if you’ve found a better way in the comments!

Full example can be found here: https://github.com/goldenshun/react-apollo-container-components

More where this came from

This story is published in Noteworthy, where thousands come every day to learn about the people & ideas shaping the products we love.

Follow our publication to see more product & design stories featured by the Journal team.

--

--