mswMock Service Worker

Life-cycle events

The life-cycle events API allows you to attach listeners to the internal library events, such as when a new request is made or a mocked response is sent. This API is primarily designed for third-party extensions or for debugging request handlers.

Life-cycle events happen post-factum and cannot affect related requests/responses.

This API is not meant for mocking. Use setupWorker/setupServer instead.

Usage

The life-cycle events API is exposed under the .events property of the worker/server instance:

1const worker = setupWorker(...handlers)
2worker.events
3
4// or
5
6const server = setupServer(...handlers)
7server.events

The API itself resembles the EventEmitter but is stripped down to the following methods:

  • on, to react to a given event;
  • removeListener, to remove a listener for a given event;
  • removeAllListeners, to remove all listeners.

Such limitation is intended, as you mustn't emit internal events—that is done for you by the library.

Methods

on

Use the .on method to add an event listener to a given life-cycle event.

1worker.events.on('request:start', (req) => {
2 console.log(req.method, req.url.href)
3})

removeListener

Removes the listener for the given life-cycle event.

Removing listeners is particularly handy to reset the events introspection during tests.

1// Previously attached listener.
2const listener = () => console.log('new request')
3worker.events.on('request:start', listener)
4
5worker.events.removeListener('request:start', listener)

removeAllListeners

Removes all life-cycle events listeners.

1worker.events.removeAllListeners()

Optionally, accepts a life-cycle event name to specify which group of listeners to remove, in case there are multiple.

1// Removes all listeners for the "request:start" event,
2// but keeps all the other listeners.
3worker.events.removeAllListeners('request:start')

Events

request:start

A new request has been dispatched.

Parameter nameTypeDescription
reqMockedRequestA request representation.
1worker.events.on('request:start', (req) => {
2 console.log('new request:', req.method, req.url.href)
3})

request:match

A dispatched request has a corresponding request handler.

Parameter nameTypeDescription
reqMockedRequestA request representation.
1worker.events.on('request:match', (req) => {
2 console.log('%s %s has a handler!', req.method, req.url.href)
3})

request:unhandled

A dispatched request doesn't have any corresponding request handler.

Parameter nameTypeDescription
reqMockedRequestA request representation.
1worker.events.on('request:unhandled', (req) => {
2 console.log('%s %s has no handler', req.method, req.url.href)
3})

request:end

A request has ended. Emits for all requests regardless if their responses were mocked or bypassed.

Parameter nameTypeDescription
reqMockedRequestA request representation.
1worker.events.on('request:end', (req) => {
2 console.log('%s %s ended', req.method, req.url.href)
3})

response:mocked

A mocked response has been sent.

Parameter nameTypeDescription
resResponse (worker), IsomorphicResponse (server)The response that's been sent.
reqIdstringUUID of the associated request.
1worker.events.on('response:mocked', async (res, reqId) => {
2 const responseText = await res.text()
3 console.log('sent a mocked response', reqId, responseText)
4})
Note that the res instance differs between the browser and Node.js. Take this difference into account when operating with it.
1server.events.on('response:mocked', (res, reqId) => {
2 const responseText = res.body
3 console.log('sent a mocked response', reqId, responseText)
4})

response:bypass

An original response has been sent.

Parameter nameTypeDescription
resResponse (worker), IsomorphicResponse (server)The response that's been sent.
reqIdstringUUID of the associated request.
1worker.events.on('response:bypass', async (res, reqId) => {
2 const responseText = await res.text()
3 console.log('sent an original response', reqId, responseText)
4})

Just as with the response:mocked event, the res instance is different between the browser and Node.js.

1server.events.on('response:bypass', (res, reqId) => {
2 const responseText = res.body
3 console.log('sent an original response', reqId, responseText)
4})

Examples

Tracking a request

Although each life-cycle events emits in isolation, it includes a request ID (reqId) parameter to connect related requests and responses.

1const allRequests = new Map()
2
3worker.events.on('request:start', (req) => {
4 const { id } = req
5 // Store this request by id in an internal Map.
6 allRequests.set(id, req)
7})
8
9worker.events.on('response:mocked', (res, reqId) => {
10 // Get a request associated with this response.
11 const req = allRequests.get(reqId)
12})

Asserting request payload

The life-cycle events API can be used for retrieving a request reference and writing request assertions (i.e. whether the correct request payload was sent).

Request assertions should be avoided whenever possible. Always prefer testing the logic/UI dependent on the response to ensure request/response validity. Read more about Request assertions.
1import { matchRequestUrl } from 'msw'
2import { setupServer } from 'msw/node'
3import { handlers } from './handlers'
4
5const server = setupServer(...handlers)
6
7function waitForRequest(method: string, url: string) {
8 let requestId = ''
9
10 return new Promise((resolve, reject) => {
11 server.events.on('request:start', (req) => {
12 const matchesMethod = req.method.toLowerCase() === method.toLowerCase()
13 const matchesUrl = matchRequestUrl(url, req.url)
14
15 if (matchesMethod && matchRequestUrl) {
16 requestId = req.id
17 }
18 })
19
20 server.events.on('request:match', (req) => {
21 if (req.id === requestId) {
22 resolve(req)
23 }
24 })
25
26 server.events.on('request:unhandled', (req) => {
27 if (req.id === requestId) {
28 reject(
29 new Error(`The ${req.method} ${req.url.href} request was unhandled.`),
30 )
31 }
32 })
33 })
34}
35
36beforeAll(() => server.listen())
37afterAll(() => server.close())
38
39it('sends the user credentials to the backend', async () => {
40 // Establish a request listener but don't resolve it yet.
41 const pendingRequest = waitForRequest('POST', '/login')
42
43 // Perform the request itself.
44 // Here you would use any tested code that makes a request.
45 await fetch('/login', {
46 method: 'POST',
47 headers: {
48 'Content-Type': 'application/json',
49 },
50 body: JSON.stringify({
51 email: 'user@example.com',
52 password: 'super+secret123',
53 }),
54 })
55
56 // Await the request and get its reference.
57 const request = await pendingRequest
58
59 expect(request.body).toEqual({
60 email: 'user@example.com',
61 password: 'super+secret123',
62 })
63})