graphql-query-test-mock
A library to mock GraphQL queries when testing clients making calls to GraphQL-API:s. Ideal for use with something like Jest and Relay Modern/Apollo, but it's not bound to any specific client library.
Focus is on realistic testing. We use nock
to do an as realistic mock as possible - requests will actually be dispatched from your client code, but intercepted at the http
level by nock
, allowing
for an as realistic testing environment as you can get without making requests to an actual server.
A few things this library helps with:
- It helps you mock your GraphQL queries for you client.
- It helps you control that your queries and mutations are correct by matching variables in your requests, making sure you pass the correct variables.
- It lets you control when queries resolve, so you can make sure your loading and intermediate states all appear correct.
- It lets you simulate the API responding with statuses like 401/500, allowing you to test token refreshes, error states, and so on.
- It lets you resolve a sequence of responses, allowing you to test more complex scenarios like pagination and multiple executions of the same query returning different data.
Installation
You need node >= 8
in order to use this library.
# nock is a peer dependency, please install that as well
yarn add graphql-query-test-mock nock --dev
You'll also need to have a fetch
implementation available in your environment (jest
does not come with one by default). You could for example use node-fetch
and put something like
this in your test setup file:
global.fetch = require('node-fetch');
Usage
Below is a detailed instruction on how to use the query mock. There's also an example repo located here which you can clone and check out for yourself.
Setup
These instructions will assume you're using Jest, but adapting to other test frameworks should not be hard.
Create your QueryMock
First, create a file somewhere you can import from your tests. This file should create a QueryMock and export it, looking something like this:
import { QueryMock } from 'graphql-query-test-mock';
export const queryMock = new QueryMock();
You will use this exported queryMock in your tests.
Make sure the query mock is initialized and reset between each test
In order for old mocks to not stick around between tests, we'll need to set up and tear down our mock between each test. We'll use Jests setupTestFramework
file to accomplish that.
setupTestFramework
allow us to run stuff before each test, allowing us to reset all query mocks and set up the new, fresh mock.
Please follow Jests instructions on adding those files to your project, then continue here.
import { queryMock } from '../path/to/your/queryMock';
beforeEach(() => {
queryMock.setup(GRAPHQL_API_URL);
});
Now we're all set up and ready to mock queries!
Mocking queries
Check out a fairly complete and exhaustive example of what can be done with this library here.
Below is a a simple example using react-testing-library
, but I do encourage you to look at the repo linked above.
import { queryMock } from '../path/to/your/queryMock';
import { wait, render } from 'react-testing-library';
import React from 'react';
describe('Some test Relay Modern test', () => {
it('should render the logged in users name', async () => {
queryMock.mockQuery({
name: 'StartQuery',
variables: {
userId: '123'
},
data: {
user: {
id: '123',
name: 'Some Name'
}
}
});
const r = render(<Start userId="123" />);
await wait(() => r.getByText('Some Name'));
expect(r.queryByText('Some Name')).toBeTruthy();
});
});
API Reference
QueryMock
Create a new queryMock
by doing: new QueryMock()
. You then use queryMock
to handle your mocks.
queryMock.setup(graphQLURL: string)
Sets up mocking on the specified GraphQL API URL. Make sure you run this before your test suite.
queryMock.reset()
Resets the queryMock
, meaning all mocks are removed, and the call history is erased.
Run this before/after each test to make sure you always start fresh.
queryMock.mockQuery(config)
Mocks a query. config
looks like this:
type MockGraphQLConfig = {|
name: string,
data: Data,
graphqlErrors?: Array<Error>,
error?: Data,
status?: number,
matchOnVariables?: boolean,
matchVariables?: (variables: Variables) => boolean | Promise<boolean>,
variables?: Variables,
ignoreThesePropertiesInVariables?: Array<string>,
persist?: boolean,
customHandler?: (
req: any,
config: {|
query: string,
operationName: string,
variables: ?Variables
|}
) => [number, ServerResponse] | Promise<[number, ServerResponse]>,
changeServerResponse?: ChangeServerResponseFn
|};
queryMock.mockQueryWithControlledResolution
The same as mockQuery
above, but returns a function that lets you manually control when you
want the mock server to resolve the response. Useful when testing loading states and similar things.
Example:
const resolveResponse = queryMock.mockQueryWithControlledResolution(...config...);
expect(getElementByText('Loading....')).toBeTruthy();
resolveResponse();
expect(getElementByText('Some text from the API.')).toBeTruthy();
queryMock.getCalls()
Returns Array<RecordedGraphQLQuery>
where RecordedGraphQLQuery
looks like this:
type RecordedGraphQLQuery = {|
id: string,
variables: ?Variables,
headers: { [key: string]: string },
response: ServerResponse
|};
Useful for various things, for instance checking whether the correct headers are sent:
const lastCall = queryMock.getCalls().pop();
expect(lastCall.headers['Authorization']).toBe('Bearer mock-token');
queryMock.cleanup()
Runs a cleanup on nock to re-enable real network requests on the mocked endpoint.
FAQ
Why use nock
, why not just mock fetch
?
The goal of this library is to get as close as possible to how your queries would
be run in production. Mocking using nock
ensures that we test that an actual request
is sent to the correct URL.
How to I handle unstable variables like Date or random things that change for every query?
This is a pretty common thing, for instance when making queries that filter using dates.
You can do something like this to solve it:
queryMock.mockQuery({
name: 'SomeQueryName',
variables: {
aPropThatIsStable: true
},
ignoreThesePropertiesInVariables: ['fromDate', 'toDate'],
data: dataYouWantToReturn
});
This makes sure that the query is matched on variables, but ignores "fromDate" and "toDate", which otherwise
would never match since they're relative and likely change over some period of time.
Creating "real" mock data is very cumbersome! Anything I can do to speed it up?
My personal trick is to add a console.log
inside of your fetch
for your framework, like this:
...
.then(res => {
if (__DEV__) {
console.log({
name: operation.name,
variables,
data: res
});
}
This makes it easy to copy the variables
and data
from the actual server response.