Mock Service Worker
  1. Recipes
  2. Custom request handler

Custom request handler

Use case

Creating a custom request handler is not something you would do most of the times when working with the library. However, there are cases when the usage of the standard request handlers (rest.*/graphql.*) is insufficient or inapplicable. A vibrant example of such use case is API mocking for an SDK.

When using an SDK you are provided with an interface that wraps request calls in a more domain-driven way. For example:

1// A random "externalSDK" package
2// that allows us to list some resources.
3externalSDK.listResources()

With SDK being an abstraction over requests, the exact method and endpoint used under the hood become its implementation details. It's highly discouraged to base your testing on such implementation details, as they are subjected to change and are not exposed publicly for a reason.

Instead, you can target a request made by an SDK by implementing a custom request matching and resolving logic with MSW.

Request handler API

1interface RequestHandler<
2 RequestType,
3 ContextType,
4 ParsedRequest,
5 PublicRequest,
6 ResponseBodyType
7> {
8 parse?(req: RequestType): ParsedRequest | null
9
10 predicate(req: RequestType, parsedReq: ParsedRequest): boolean
11
12 getPublicRequest?(
13 req: RequesType,
14 parsedRequest: ParsedRequest,
15 ): PublicRequest
16
17 resolver: ResponseResolver
18
19 defineContext?(req: PublicRequest): ContextType
20
21 log(
22 req: PublicRequest,
23 res: ResponseWithSerializedHeaders,
24 handler: RequestHandler<
25 RequestType,
26 ContextType,
27 ParsedRequest,
28 PublicRequest,
29 ResponseBodyType
30 >,
31 parsedRequest: ParsedRequest,
32 ): void
33}

parse

Parses a request, providing the result to the rest of the request handler's lifecycle methods.

Useful for deriving or normalizing request payload for the rest of the processing chain (i.e. getting an operation name, transforming body properties).

getPublicRequest

Maps the captured and parsed request data to the publicly exposed req object in the response resolver.

Useful to expose certain parsed request information, while preserving the rest for the internal purposes. For example, you can parse GraphQL variables in the parse method and expose them in the getPublicRequest later, so they could be references under req.variables in the response resolver:

1{
2 parse(req) {
3 // Everything returned from the `parse` function
4 // is available only internally via the `parsedRequest` object.
5 return {
6 variables: parseVariables(req.body)
7 }
8 },
9 getPublicRequest(req, parsedRequest) {
10 // Everything returned from the `getPublicRequest` function
11 // is available publicly under the `req` object
12 // in the response resolver.
13 return {
14 ...req,
15 variables: parsedRequest.variables
16 }
17 }
18}

predicate

Determines whether the captured request must be responded with a mocked response.

resolver

Returns a mocked response for the matched request.

Read more in the response resolver section.

defineContext

Overrides the default context (ctx) object available in the response resolver.

Useful to provide custom response transformers specific to the ...

1export const customHandler = (resolver) => {
2 return {
3 // ...predicate and other properties
4 resolver,
5 defineContext(req, parsedRequest) {
6 return {
7 resource(payload) {
8 return (res) => {
9 res.headers.set('content-type', 'application/json')
10 res.body = {
11 ...payload,
12 resourceId: getRandomId(),
13 }
14 return res
15 }
16 },
17 }
18 },
19 }
20}

The example above would expose the ctx.resource() function in the response resolver, allowing for a shorthand resource creation:

1customHandler((req, res, ctx) => {
2 return res(ctx.resource({ title: 'Random resource' }))
3})

You can utilize default contexts, such as defaultContext, restContext and graphqlContext exposed from the msw package.

log

Prints out the resolved request and its response to browser's console.

Create a handler

Getting back to our SDK example, let's say that the externalSDK.listResources() produces the following request:

1GET /sdk/resources
2
3Headers:
4 Content-Type: "application/json"
5
6Body:
7 {
8 "version": "1.5",
9 "operation": "ListResources",
10 "region": "ua"
11 }

When creating a custom request handler it's crucial to define reliable data for the request matching logic. As the endpoint string is an implementation detail of the SDK, the operation request body property is a description of the SDK method that's been invoked.

Let's create a request handler that would allow to capture requests based on their operation name:

1export const sdk = (operation, resolver) => {
2 return {
3 predicate(req) {
4 // Match requests which "operation" body property
5 // equals to the given "operation" string of the handler.
6 const req.body.operation === operation
7 },
8 // Propagate the response resolver as-is.
9 resolver,
10 // Print out the matched request in browser's console.
11 log(req, res) {
12 console.log('Request', req)
13 console.log('Response', res)
14 }
15 }
16}

Note that you may want to narrow down the custom predicate function by utilizing other request data (headers, body payload) to reduce the chance of false positive matches.

Provide the custom sdk request handler to the setupWorker/setupServer calls, just as you would do with the standard handlers.

1import { setupWorker } from 'msw'
2import { sdk } from './handlers/sdk'
3
4const worker = setupWorker(
5 sdk('ListResources', (req, res, ctx) => {
6 return res(ctx.json({ id: 1 }))
7 }),
8)
9
10worker.start()