- Introduction
- Getting started
- Philosophy
- Comparison
- Limitations
- Debugging runbook
- FAQ
- Basics
- Concepts
- Network behavior
- Integrations
- API
- CLI
- Best practices
- Recipes
- Cookies
- Query parameters
- Response patching
- Polling
- Streaming
- Network errors
- File uploads
- Responding with binary
- Custom worker script location
- Global response delay
- GraphQL query batching
- Higher-order resolver
- Keeping mocks in sync
- Merging Service Workers
- Mock GraphQL schema
- Using CDN
- Using custom "homepage" property
- Using local HTTPS
GraphQL query batching
Intercept and mock batched GraphQL queries.
Query batching is a performance mechanism provided by some GraphQL clients to optimize the number of operations made by grouping them togeher in a single query. While this feature has its practical benefits, query batching is not a part of the GraphQL specification (neither the GraphQL-over-HTTP specification), lacking any standard consensus on the syntax and behavior of batched queries. Because of this, MSW does not provide a built-in way of handling such queries.
We highly recommend implementing the support for batched GraphQL queries as a part of your MSW setup. Below, you can find a couple of examples of how to achieve that.
General knowledge
At its core, mocking a batched GraphQL query comes down to the following steps:
- Intercept the batched GraphQL query;
- Unwrap the batched query into individual GraphQL queries;
- Resolve the individual queries against the existing request handlers;
- Compose the batched response.
Apollo
Apollo provides Query batching by grouping multiple queries in a single root-level array.
[
query GetUser {
user {
id
}
},
query GetProduct {
product {
name
}
}
]
This grouping is later reflected in the payload structure received in response to a batched query:
[
{ "data": { "user": { "id": "abc-123" } } },
{ "data": { "product": { "name": "Hoover 2000" } } }
]
You can mock batched GraphQL queries in Apollo by introducing a custom batchedGraphQLQuery
higher-order request handler that intercepts such batched queries, unwraps them, and resolves them against any given list of request handlers using the getResponse
function from msw
.
import { http, HttpResponse, getResponse, bypass } from 'msw'
function batchedGraphQLQuery(url, handlers) {
return http.post(url, async ({ request }) => {
const payload = await request.clone().json()
// Ignore non-batched GraphQL queries.
if (!Array.isArray(payload)) {
return
}
const responses = await Promise.all(
payload.map((query) => {
// Construct an individual query request
// to the same URL but with an unwrapped query body.
const queryRequest = new Request(request, {
body: JSON.stringify(operation),
})
// Resolve the individual query request
// against the list of request handlers you provide.
const response = await getResponse({
request: queryRequest,
handlers,
})
// Return the mocked response, if found.
// Otherwise, perform the individual query as-is,
// so it can be resolved against an original server.
return response || fetch(bypass(queryRequest))
})
)
// Read the mocked response JSON bodies to use
// in the response to the entire batched query.
const queryData = await Promise.all(
responses.map((response) => response?.json()),
)
return HttpResponse.json(queryData)
})
}
Then, use the batchedGraphQLQuery
function in your request handlers:
import { graphql, HttpResponse } from 'msw'
const graphqlHandlers = [
graphql.query('GetUser', () => {
return HttpResponse.json({
data: {
user: { id: 'abc-123' },
},
})
}),
]
export const handlers = [
batchedGraphQLQuery('/graphql', graphqlHandlers),
...graphqlHandlers,
]
batched-execute
The batched-execute
package provides Query batching by hoisting multiple operations on a single query and achieving grouping by using field aliases.
query {
user_0: user {
id
}
product_0: product {
name
}
}
The client then remap the field aliases to the original operations, producing a flat response object.
You can mock batched GraphQL queries in batched-execute
by introducing a custom batchedGraphQLQuery
higher-order request handler that intercepts such batched queries and resolves them against a mocked schema. We recommend a schema-first API mocking in this case to support anonymous queries.
import {
buildSchema,
print,
graphql as executeGraphQL,
defaultFieldResolver,
} from 'graphql'
import { http, HttpResponse, bypass } from 'msw'
// Describe the GraphQL schema.
// You can also use an existing schema!
const schema = buildSchema(`
type User {
id: ID!
}
type Query {
user: User
}
`)
function batchedGraphQLQuery(url, handlers) {
return http.post(url, async ({ request }) => {
const payload = await request.json()
// Resolve the intercepted GraphQL batched query
// against the mocked GraphQL schema.
const result = await executeGraphQL({
source: payload.query,
variableValues: data.variables,
schema,
rootValue: {
// Mock individual queries, fields, and types.
user: () => ({ id: 'abc-123' }),
},
async fieldResolver(source, args, context, info) {
// Resolve the known fields from the "rootValue".
if (source[info.fieldName]) {
return defaultFieldResolver(source, args, context, info)
}
// Proxy the unknown fields to the actual GraphQL server.
const compiledQuery = info.fieldNodes
.map((node) => print(node))
.join('\n')
const query = `${info.operation.operation} { ${compiledQuery} }`
const queryRequest = new Request(request, {
body: JSON.stringify({ query }),
})
const response = await fetch(bypass(queryRequest))
const { error, data } = await response.json()
if (error) {
throw error
}
return data[info.fieldName]
},
})
return HttpResponse.json(result)
})
}