I've recently found myself wanting to add unit tests for an Apollo Client mutation that had a complex optimistic update behavior (where I had to read and write a few fragments manually to apply the updated states) and I wasn't able to find any official docs about how you can test an Apollo client cache update.
After a few tentatives, what I ended up doing is passing to the cache
field of the <MockProvider>
a object containing the state of the cache I want to use to test the update.
If you're using the apollo-cache-inmemory
library like I'm doing, I would suggest you to:
- Run your app in development mode (
process.env.NODE_ENV !== 'production'
) so you'll be able to access the global__APOLLO_CLIENT__
field. - Get to the specific state of the app that you want to test.
- Grab the Apollo cache state from the
InMemoryCache
by runningJSON.stringify(__APOLLO_CLIENT__.cache.read())
in the console. - Copy the output from the console, paste it in a
.js
or.json
file and manually edit it if/where needed.
If you're not familiar with the state representation used by the Apollo cache I would suggest you to play a bit with the Apollo Client Devtools by exploring the "Cache" tab and seeing how it changes when you run an Apollo query on your app.
Now it's all a matter of providing to the MockProvider
's cache
field the data that you just extracted (so that it can be used as its initial cache state) and you'll be all set for testing your cache update.
Here's a simplified example of a unit test:
import React from "react";
import { MockedProvider } from "react-apollo/test-utils";
import { UpdateItemMutation, UPDATE_ITEM_MUTATION } from "./UpdateItemMutation";
import initialCacheState from "./my-cache.json".
// Utility that can be used to wait for a response of an Apollo query/mutation.
// See:
// https://www.apollographql.com/docs/react/recipes/testing/#testing-final-state
const waitForResponse = () => new Promise(res => setTimeout(res,0));
describe("<UpdateItemMutation />", () => {
it("should update the cache correctly when triggered", async () => {
// 1. Mock the cache mechanism you're using in your app.
// In my case I'm using the `InMemoryCache`, which can be rehydrated
// using the `restore` method.
// If in you're app you're customizing the `InMemoryCache` instance I would
// suggest you to export it and import in here.
const cache = new InMemoryCache().restore(initialCacheState);
// 2. Setup the data you'll need to test
const itemId = "abc123"; // ID of the interested item in the cache
// 3. Mock your mutation.
const mutationMock = {
request: {
query: UPDATE_ITEM_MUTATION,
variables: { itemId: threadId }
},
result: {
data: {
__typename: "Mutation",
// Mock here the expected result of your query.
updateitem: {
__typename: "Item",
id: itemId,
// ...additional data
}
}
}
};
// 4. Render the <UpdateItemMutation /> component
const { getByTestId } = render(
<MockedProvider cache={cache} mocks={[mutationMock]}>
<UpdateItemMutation id={itemId}>
{mutate => <div data-testid="button-mutate" onClick={mutate} />}
</UpdateItemMutation>
</MockedProvider>
);
// 5. Fire the mutation and wait for it to be completed
const button = getByTestId("button-mutate");
fireEvent.click(button);
await waitForResponse();
// 8. You can now extract the updated query and make your assertions based
// on what you expect from the result of the mutation.
const updatedCache = cache.extract();
expect(updatedCache[`Item:${itemId}`].something]).toBe(true).
});
});
import React from "react";
import { MockedProvider } from "react-apollo/test-utils";
import { UpdateItemMutation, UPDATE_ITEM_MUTATION } from "./UpdateItemMutation";
import initialCacheState from "./my-cache.json".
// Utility that can be used to wait for a response of an Apollo query/mutation.
// See:
// https://www.apollographql.com/docs/react/recipes/testing/#testing-final-state
const waitForResponse = () => new Promise(res => setTimeout(res,0));
describe("<UpdateItemMutation />", () => {
it("should update the cache correctly when triggered", async () => {
// 1. Mock the cache mechanism you're using in your app.
// In my case I'm using the `InMemoryCache`, which can be rehydrated
// using the `restore` method.
// If in you're app you're customizing the `InMemoryCache` instance I would
// suggest you to export it and import in here.
const cache = new InMemoryCache().restore(initialCacheState);
// 2. Setup the data you'll need to test
const itemId = "abc123"; // ID of the interested item in the cache
// 3. Mock your mutation.
const mutationMock = {
request: {
query: UPDATE_ITEM_MUTATION,
variables: { itemId: threadId }
},
result: {
data: {
__typename: "Mutation",
// Mock here the expected result of your query.
updateitem: {
__typename: "Item",
id: itemId,
// ...additional data
}
}
}
};
// 4. Render the <UpdateItemMutation /> component
const { getByTestId } = render(
<MockedProvider cache={cache} mocks={[mutationMock]}>
<UpdateItemMutation id={itemId}>
{mutate => <div data-testid="button-mutate" onClick={mutate} />}
</UpdateItemMutation>
</MockedProvider>
);
// 5. Fire the mutation and wait for it to be completed
const button = getByTestId("button-mutate");
fireEvent.click(button);
await waitForResponse();
// 8. You can now extract the updated query and make your assertions based
// on what you expect from the result of the mutation.
const updatedCache = cache.extract();
expect(updatedCache[`Item:${itemId}`].something]).toBe(true).
});
});