Testing Apollo Containers (For Apollo 2.0)
As of this writing, there is no official testing guide for react-apollo. But there are a number of unofficial blog posts and articles. Here’s a sample:
So why write another blog post? Well, after a lot of spelunking, I came to the conclusion that most react-apollo developers approach tests in one of two ways:
- They test their components with a mocked version of apollo
- They separate apollo-related code from their components into containers, and only test the non-apollo components
After experimenting with both methods, I discovered a nice way to integrate these approaches. Others may have already done this, but I haven’t seen it explicitly articulated anywhere.
The key is to split tests into two types:
- Tests that verify a component behaves correctly given certain props (component tests)
- Tests that verify a container passes down the right props given certain mocked global state (container tests)
Let’s try this on a real world example. Consider a ToDo application that allows a user to nest ToDos via a single input at the bottom of the screen. Here’s a gif of the behavior we want:
We’ll focus on the component at the bottom (the piece with the green background), which we’ll call
<TaskCreator/>. We’ll keep all of the data queries and mutations in a separate container called
<TaskCreatorContainer/>, and pass the data and mutations to
<TaskCreator/> via props.
Now lets sketch out how we want our component and our container to behave via some unimplemented specs:
The first set of tests should be straighforward to anyone who’s tested react components, so we won’t go into them here. If you’re new to testing react components, I highly recommend getting comfortable with enzyme and implementing some regular, non-container tests before proceeding.
The second set of tests is trickier. The basic idea is to test that a component receives a certain set of props given a certain global state, which includes things like apollo query results, redux state, and react-router state.
This sounds sort of like an integration test that we could implement with selenium. But if we look at what containers actually are, we see that they’re just react components that query/mutate data. Selenium tests are better suited for final user flows once we want to test a bunch of containers and APIs interacting with each other. If we want to test the API of a single container, we’ll want to test a bunch of different inputs to that API, which would be difficult and slow to do via something like selenium.
In order to test how our container responds to different global state, we’ll need to mock it out. The
<MockedProvider /> component inside of react-apollo is close to what we want, but we’ll need something that mocks out all of the global state relevant to our containers, not just apollo. Let’s call this hypothetical component
<TestProvider/>. Here’s how we’d use it:
To mock out graphql responses for apollo 2.0, we’ll use apollo-schema-link. This allows us to hit the schema directly without any network requests. Then we will use
addMockFunctionsToSchema from graphql-tools to mock out our resolvers. Both libraries are well documented and officially supported by apollo. Here’s how we’ll use them in
Now let’s add some methods to mock out redux state and record actions:
Depending on what you’re using for global state and what your containers look like, you might have some more methods in your
<TestProvider/>. In our case, all we care about is redux and apollo, so we’re ready to use our
<TestProvider/> in our container specs:
We now have implemented container specs. Hooray!
So why’d we do all that?
We can now confidently refactor GraphQL queries/fragments
- If we change a shared query or fragment, our tests ensure each dependent component is still getting the props it needs
We can now confidently refactor redux actions/reducers
- If we change an action/reducer, our tests ensure that the props which are supposed to mutate redux are still having the intended effect
We can now confidently refactor shared hocs
- If we change a shared hoc, our tests ensure each dependent component still gets the props it needs
We can now confidently refactor our schema
- If we change the schema and make a query invalid, our tests will complain instead of giving us false positives
We don’t need to know so much about apollo anymore
- If apollo changes their API (by changing/removing ‘loading’ from the data prop, for example), our tests will complain instead of giving us false positives
- If apollo changes how it parses errors, we won’t need to change our tests, since we’re throwing the same errors we’re expecting to be thrown in the resolvers.
We now can hook our component up to other data sources without rewriting tests
- If we want to use our component in some other context (maybe with a different query, or hydrated by something other than apollo), our component tests our still valid.
Comments and suggestions appreciated!
If you’d like more details, check out the full repo for the ToDo example used throughout the article.