Helps unit test components which use the Apollo Client.
Installations
npm install mock-apollo-client
Developer
mike-gibson
Developer Guide
Module System
CommonJS
Min. Node Version
Typescript Support
Yes
Node Version
20.12.2
NPM Version
10.5.0
Statistics
117 Stars
61 Commits
15 Forks
6 Watching
10 Branches
5 Contributors
Updated on 01 Oct 2024
Languages
TypeScript (97.43%)
Shell (2.13%)
JavaScript (0.43%)
Total Downloads
Cumulative downloads
Total Downloads
21,099,264
Last day
-7%
39,362
Compared to previous day
Last week
-8%
161,450
Compared to previous week
Last month
34.2%
707,368
Compared to previous month
Last year
26.8%
5,734,785
Compared to previous year
Daily Downloads
Weekly Downloads
Monthly Downloads
Yearly Downloads
Peer Dependencies
1
Dev Dependencies
7
Mock Apollo Client
Helps unit test components which use the Apollo Client.
Versions
Version 0.x of this library is compatible with Apollo client 2. View the README for 0.x here.
Version 1.x of this library is compatible with Apollo client 3 (this README)
Motivation
Whilst using the impressive @apollo/client
library, I ran into issues while trying to unit test components which used the GraphQL Query
and Mutation
components. The Apollo client library includes a MockedProvider
component which allows query and mutation results to be mocked, but didn't offer enough control within unit tests. The Apollo client 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
MockedProvider
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.
Installation
1npm install --save-dev mock-apollo-client
Usage
The examples below use React
, 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 Apollo testing docs and are written in TypeScript.
Simple Query Example
Consider the file below, which contains a single GraphQL query and a component which is responsible for rendering the result of the query:
1// dog.tsx 2 3import { gql, useQuery } from '@apollo/client'; 4import React from 'react'; 5 6export const GET_DOG_QUERY = gql` 7 query getDog($name: String) { 8 dog(name: $name) { 9 id 10 name 11 breed 12 } 13 } 14`; 15 16export const Dog: React.FunctionComponent<{ name: string }> = ({ name }) => { 17 const { loading, error, data } = useQuery( 18 GET_DOG_QUERY, 19 { variables: { name } } 20 ); 21 if (loading) return <p>Loading...</p>; 22 if (error) return <p>Error!</p>; 23 24 return ( 25 <p> 26 {data.dog.name} is a {data.dog.breed} 27 </p> 28 ); 29};
To unit test this component using mock-apollo-client
, the test file could look like the following:
1// dog.test.tsx 2 3import { ApolloProvider } from '@apollo/client'; 4import { mount, ReactWrapper } from 'enzyme'; 5import { createMockClient } from 'mock-apollo-client'; 6import * as React from 'react'; 7 8import { GET_DOG_QUERY, Dog } from './dog'; 9 10let wrapper: ReactWrapper; 11 12beforeEach(() => { 13 const mockClient = createMockClient(); 14 15 mockClient.setRequestHandler( 16 GET_DOG_QUERY, 17 () => Promise.resolve({ data: { dog: { id: 1, name: 'Rufus', breed: 'Poodle' } } })); 18 19 wrapper = mount( 20 <ApolloProvider client={mockClient}> 21 <Dog name="Rufus" /> 22 </ApolloProvider> 23 ); 24}); 25 26it('renders the dog name and breed', () => { 27 expect(wrapper.text()).toContain('Rufus is a Poodle'); 28});
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
Asserting query variables
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 library.
1const queryHandler = jest.fn().mockResolvedValue({ data: { dog: { id: 1, name: 'Rufus', breed: 'Poodle' } } }); 2 3mockApolloClient.setRequestHandler(GET_DOG_QUERY, queryHandler); 4 5// .... 6 7it('executes the query with the correct variables', () => { 8 expect(queryHandler).toBeCalledTimes(1); 9 expect(queryHandler).toBeCalledWith({ name: 'Rufus' }); 10});
Loading states
A request handler returns a promise, so testing for loading state just requires that the promise returned is not resolved or rejected.
Error states
To simulate a GraphQL network error, the request handler should return a rejected promise. i.e.
1mockApolloClient.setRequestHandler( 2 GET_DOG_QUERY, 3 () => 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.
1mockApolloClient.setRequestHandler( 2 GET_DOG_QUERY, 3 () => Promise.resolve({ errors: [{ message: 'GraphQL Error' }] }));
Mutations
Mutations can be tested the same way that queries are, by using setRequestHandler
and specifying a request handler for the mutation query.
Subscriptions
Subscriptions can be tested, but require a different setup as they receive a stream of data. Consider the file below, which contains a single subscription and a component which is responsible for rendering the updated data:
1// dogSubscription.tsx 2 3import { gql, useSubscription } from '@apollo/client'; 4import React from 'react'; 5 6export const SUBSCRIBE_DOG_DOCUMENT = gql` 7 subscription subscribeDog($name: String) { 8 dog(name: $name) { 9 id 10 name 11 numberOfBarks 12 } 13 } 14`; 15 16export const DogSubscription: React.FunctionComponent<{ name: string }> = ({ name }) => { 17 const { loading, error, data } = useSubscription( 18 SUBSCRIBE_DOG_DOCUMENT, 19 { variables: { name } } 20 ); 21 if (loading) return <p>Loading...</p>; 22 if (error) return <p>Error!</p>; 23 24 return ( 25 <p> 26 {data.dog.name} has barked {data.dog.numberOfBarks} time(s) 27 </p> 28 ); 29};
To unit test this component using mock-apollo-client
, the test file could look like the following:
1// dogSubscription.test.tsx 2 3import { ApolloProvider } from '@apollo/client'; 4import { mount, ReactWrapper } from 'enzyme'; 5import { createMockClient, createMockSubscription, IMockSubscription } from 'mock-apollo-client'; 6import { act } from 'react-dom/test-utils'; 7import * as React from 'react'; 8 9import { SUBSCRIBE_DOG_DOCUMENT, DogSubscription } from './dogSubscription'; 10 11let wrapper: ReactWrapper; 12let mockSubscription: IMockSubscription; 13 14beforeEach(() => { 15 const mockClient = createMockClient(); 16 mockSubscription = createMockSubscription(); 17 18 mockClient.setRequestHandler( 19 SUBSCRIBE_DOG_DOCUMENT, 20 () => mockSubscription); 21 22 wrapper = mount( 23 <ApolloProvider client={mockClient}> 24 <DogSubscription name="Rufus" /> 25 </ApolloProvider> 26 ); 27}); 28 29it('renders the dog details', () => { 30 act(() => { 31 mockSubscription.next({ data: { dog: { id: 1, name: 'Rufus', numberOfBarks: 0 } } }); 32 }); 33 34 expect(wrapper.text()).toContain('Rufus has barked 0 time(s)'); 35 36 act(() => { 37 mockSubscription.next({ data: { dog: { id: 1, name: 'Rufus', numberOfBarks: 1 } } }); 38 }); 39 40 expect(wrapper.text()).toContain('Rufus has barked 1 time(s)'); 41});
The subscription can be closed by calling .complete
if necessary for the test.
Errors
You can also test error states by calling .error
on the mockSubscription
and passing errors as described in Error States:
1mockSubscription.error(new Error('GraphQL Network Error'))
Multiple subscriptions
A mock subscription will only be associated with a single invocation of a query. If a component is subscribing to the same query multiple times, then a separate mock subscription should be used for each one.
1const subscriptions: IMockSubscription[] = []; 2 3mockClient.setRequestHandler( 4 SUBSCRIBE_DOG_DOCUMENT, 5 () => { 6 const subscription = createMockSubscription(); 7 subscriptions.push(subscription); 8 return subscription; 9 }); 10 11... 12 13subscriptions.forEach((s) => s.next({ data: { dog: { id: 1, name: 'Rufus', numberOfBarks: 1 } } }));
Specifying Apollo client options
The createMockClient
method can be provided with the same constructor arguments that ApolloClient
accepts which are used when instantiating the mock Apollo client.
For example, to specify the cache (and possible types for fragment matching) that should be used:
1const cache = new InMemoryCache({ 2 possibleTypes: myPossibleTypes, 3}); 4 5const mockClient = createMockClient({ cache });
Additionally, you can specify a missingHandlerPolicy
to define the behavior of the mock client when a request handler for a particular operation is not found.
The missingHandlerPolicy
accepts one of three string values:
'throw-error'
: The client throws an error when it encounters a missing handler.'warn-and-return-error'
: The client logs a warning message in the console and returns an error.'return-error'
: The client returns an error without any warning message.
Here's an example of how you can set the missingHandlerPolicy
:
1const mockClient = createMockClient({ missingHandlerPolicy: 'warn-and-return-error' });
In this example, if a request handler for a given operation is not found, the client will log a warning message to the console and then return an error.
Note: it is not possible to specify the link
to use as this is how mock-apollo-client
injects its behaviour.
Fragments
If your queries or mutations use fragments against union or interface types, you must inject a cache object when creating the mock client which has been provided with possibleTypes
, and also include the correct __typename
field when mocking the response.
For example:
1import { InMemoryCache } from '@apollo/client'; 2import { createMockClient } from 'mock-apollo-client'; 3 4const cache = new InMemoryCache({ 5 possibleTypes: { 6 Hardware: ['Memory', 'Cpu'], 7 }, 8}); 9 10const mockClient = createMockClient({ cache });
You must then ensure that the query result includes the __typename
field as it would when calling your actual GraphQL API. This is to ensure that the fragment matching works as expected:
1const query = gql` 2 query Hardware { 3 hardware { 4 id 5 ... on Memory { 6 size 7 } 8 ... on Cpu { 9 speed 10 } 11 } 12 } 13`; 14 15const mockData = { 16 hardware: { 17 __typename: 'Memory', 18 id: 2, 19 size: '16gb', 20 }, 21}; 22 23const requestHandler = jest.fn().mockResolvedValue({ data: mockData });
No vulnerabilities found.
Reason
no binaries found in the repo
Reason
no dangerous workflow patterns detected
Reason
license file detected
Details
- Info: project has a license file: LICENSE.md:0
- Info: FSF or OSI recognized license: MIT License: LICENSE.md:0
Reason
4 existing vulnerabilities detected
Details
- Warn: Project is vulnerable to: GHSA-grv7-fg5c-xmjg
- Warn: Project is vulnerable to: GHSA-3xgq-45jj-v275
- Warn: Project is vulnerable to: GHSA-952p-6rrq-rcjv
- Warn: Project is vulnerable to: GHSA-3h5v-q93c-6h6q
Reason
dependency not pinned by hash detected -- score normalized to 2
Details
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:15: update your workflow using https://app.stepsecurity.io/secureworkflow/Mike-Gibson/mock-apollo-client/build.yml/master?enable=pin
- Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/build.yml:17: update your workflow using https://app.stepsecurity.io/secureworkflow/Mike-Gibson/mock-apollo-client/build.yml/master?enable=pin
- Warn: npmCommand not pinned by hash: runTestSuite.sh:25
- Warn: npmCommand not pinned by hash: runTestSuite.sh:39
- Info: 0 out of 2 GitHub-owned GitHubAction dependencies pinned
- Info: 1 out of 3 npmCommand dependencies pinned
Reason
Found 3/23 approved changesets -- score normalized to 1
Reason
0 commit(s) and 0 issue activity found in the last 90 days -- score normalized to 0
Reason
detected GitHub workflow tokens with excessive permissions
Details
- Warn: no topLevel permission defined: .github/workflows/build.yml:1
- Info: no jobLevel write permissions found
Reason
no effort to earn an OpenSSF best practices badge detected
Reason
security policy file not detected
Details
- Warn: no security policy file detected
- Warn: no security file to analyze
- Warn: no security file to analyze
- Warn: no security file to analyze
Reason
project is not fuzzed
Details
- Warn: no fuzzer integrations found
Reason
SAST tool is not run on all commits -- score normalized to 0
Details
- Warn: 0 commits out of 16 are checked with a SAST tool
Score
3.6
/10
Last Scanned on 2024-11-18
The Open Source Security Foundation is a cross-industry collaboration to improve the security of open source software (OSS). The Scorecard provides security health metrics for open source projects.
Learn More