Mocking GraphQL API
Pre-requisites
Since we will be working with a GraphQL API, we need to have a GraphQL client installed and configured in our application. We need that client primarily to dispatch queries and mutations. Please refer to the getting started steps of the respective client.
Install and configure a GraphQL client
Here are some GraphQL clients to consider:
Supported clients
Any GraphQL client that complies with the GraphQL operations specification can be used with Mock Service Worker. A client must dispatch requests with the following signature:
1interface GraphQLRequest {2 query: string3 variables?: Record<string, any>4}
Imports
In our src/mocks/handlers.js
file let's import the essentials we need for mocking a GraphQL API. They are grouped under the graphql
namespace exposed by the library.
Import graphql
from the msw
package:
1// src/mocks/handlers.js2import { graphql } from 'msw'
Request handler
To handle a GraphQL request we need to specify its operation kind (query/mutation).
In this tutorial we will be mocking a basic login flow for our user. This flow implies handling two operations:
Login
mutation, to allow a user to log in,GetUserInfo
query, to return the information about the logged in user.
Create request handlers by calling graphql[OPERATION_KIND]
and providing an operation name:
1// src/mocks/handlers.js2import { graphql } from 'msw'34export const handlers = [5 // Handles a "Login" mutation6 graphql.mutation('Login', null),78 // Handles a "GetUserInfo" query9 graphql.query('GetUserInfo', null),10]
Response resolver
To respond to an operation using a mocked response we have to specify it using a response resolver function.
Response resolver is a function that accepts the following arguments:
req
, an information about a matching request;res
, a functional utility to create the mocked response;ctx
, a group of functions that help to set a status code, headers, data, etc. on the mocked response.
In GraphQL we describe the expected response in a query/mutation declaration itself. Let's design the shape of our response for both operations:
1# Authenticates using a given username2mutation Login($username: String!) {3 login(username: $username) {4 username5 }6}
1# Returns the information about the authenticated user2query GetUserInfo() {3 user {4 username5 firstName6 }7}
Provide response resolvers to the previously defined request handlers:
1// src/mocks/handlers.js2import { graphql } from 'msw'34export const handlers = [5 // Handles a "Login" mutation6 graphql.mutation('Login', (req, res, ctx) => {7 const { username } = req.variables8 sessionStorage.setItem('is-authenticated', username)910 return res(11 ctx.data({12 login: {13 username,14 },15 }),16 )17 }),1819 // Handles a "GetUserInfo" query20 graphql.query('GetUserInfo', (req, res, ctx) => {21 const authenticatedUser = sessionStorage.getItem('is-authenticated')2223 if (!authenticatedUser) {24 // When not authenticated, respond with an error25 return res(26 ctx.errors([27 {28 message: 'Not authenticated',29 errorType: 'AuthenticationError',30 },31 ]),32 )33 }3435 // When authenticated, respond with a query payload36 return res(37 ctx.data({38 user: {39 username: authenticatedUser,40 firstName: 'John',41 },42 }),43 )44 }),45]
Utilize things like
sessionStorage
,localStorage
, or IndexedDB to handle more complex API scenarios and user interactions.
Some GraphQL clients (i.e. Apollo) may require you to include the __typename
property on each individual type on the mocked response:
1ctx.data({2 user: {3 firstName: 'John',4 lastName: 'Maverick',5 __typename: 'User'6 }7})
Follow the response shape of your GraphQL client to ensure you produce a compatible mocked response.
Next step
The request handlers are complete, yet there is one last step to perform: integrate the mocking.