- 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
Debugging runbook
Debug common issues with MSW.
Below you can find the most common issues that developers experience when integrating Mock Service Worker into their applications. Please read through this page before opening an issue on GitHub as there’s a decent chance there is an answer to your issue below.
Before you begin
Check Node.js version
Check the version of Node.js your project is using:
node -v
If it’s lower than Node.js v18, upgrade to the latest Node.js version. We do not look into issues happening on unsupported versions of Node.js.
Check MSW version
First, check what version of the msw
package you have installed:
npm ls msw
Then, check the latest publish version:
npm view msw version
If these two differ, upgrade the msw
version in your project and see if the issue persists.
Debugging runbook
You’ve checked the environment and MSW versions but the issue still persists. It’s time to do some debugging. Below, you can find a step-by-step debugging runbook to follow when experiencing any unexpected behavior with MSW.
Step 1: Verify setup
First, verify that MSW is correctly set up. Take the worker
/server
instance and add a new request:start
life-cycle event listener to it.
server.events.on('request:start', ({ request }) => {
console.log('Outgoing:', request.method, request.url)
})
You can learn more about the Life-cycle events API.
With this listener in place, you should see the console message on every outgoing request that MSW intercepts. The console message should look like this:
Outgoing: GET https://api.example.com/some/request
Outgoing: POST http://localhost/post/abc-123
You must see the request’s method and the absolute request URL. If the request method is different, adjust your request handler to reflect it. If you see a relative request URL, your request client or your testing environment are not configured correctly. You must configure the base URL option of your request client to produce absolute request URLs, and your test environment to have document.baseURI
set.
Otherwise, verify that the problematic request is printed. If it is, continue to the next step.
If the problematic request is the only request your application makes, try adding a dummy fetch call anywhere after MSW setup to confirm this step. For example:
fetch('https://example.com')
If there’s no message printed for the problematic request (or any requests), MSW is likely not being set up correctly and cannot intercept the requests. Please refer to the Integration instructions and make sure you are setting up the library as illustrated there.
Step 2: Verify handler
Go to the request handler you’ve created for the problematic request and add a console statement in its resolver function.
// src/mocks/handlers.js
import { http } from 'msw'
export const handlers = [
http.get('/some/request', ({ request }) => {
console.log('Handler', request.method, request.url)
// The rest of the response resolver here.
}),
]
You should see this console message when the problematic request happens on the page/tests. If you do, continue to the next step.
If there’s no message, MSW is able to intercept the request but cannot match it against this handler. This likely means your request handler’s predicate doesn’t match the actual request URL. Verify that the predicate is correct. Some of the common issues include:
- Using an environment variable in the path, which is not set in tests/CI (e.g.
http.get(BASE_URL + '/path'))
. Inspect any dynamic segments of the request path and make sure they have expected values; - Typos in the request path. Carefully examine the request printed in the previous step of this runbook and find any typos/mistakes in it.
If unsure, please read through the documentation on intercepting requests with MSW:
Intercepting requests
Learn about request interception and how to capture REST and GraphQL requests.
Step 3: Verify response
If the request handler is invoked but the request still doesn’t get the mocked response, the next place to check is the mocked response itself. In the request handler, jump to the mock response(s) you define.
// src/mocks/handlers.js
import { http, HttpResponse } from 'msw'
export const handlers = [
http.get('/some/request', ({ request }) => {
console.log('Handler', request.method, request.url)
return HttpResponse.json({ mocked: true })
}),
]
Verify that you are constructing a valid response. You can assign the response to a variable and print it out to inspect. You can also do an early return of a dummy mocked response and see if it’s received by your application.
If unsure, please read about mocking responses with MSW:
Mocking responses
Learn about response resolvers and the different ways to respond to a request.
If you haven’t uncovered any issues with the mocked response, proceed to the next step.
Step 4: Verify application
If none of the previous steps have proven fruitful, it’s likely the issue is in the request/response handling logic of your application. Go to the source code that performs the request and handles the response, and verify they are correct. Please follow the guidelines of your request framework carefully to make sure you are performing requests as intended.
If the problem persists, open a new issue on GitHub and provide a minimal reproduction repository. Issues without the reproduction repository where the problem can be reliably reproduced will be automatically closed.
Common issues
ReferenceError: fetch
is not defined
This error indicates that the global fetch
function is not defined in the current process. This may happen for two reasons:
- You are using an older version of Node.js (< v17);
- Your environment suppresses that global function.
Upgrading Node.js
First, check what version of Node.js you are using by running this command in the same terminal you received the fetch
error:
node -v
If the version printed is lower than v17, upgrade to the latest Node.js version
The newer Node.js versions ship with the global Fetch API, which includes the global fetch
function.
Fixing environment
Some tools, like Jest, meddle with your Node.js environment, forcefully removing present globals. If you are using such tools, make sure you add those globals back in their configurations.
Here’s an example of how to configure Jest to work with the global Fetch API in Node.js.
Make sure you are using Node.js v18 or newer before reading further.
This issue is caused by your environment not having the Node.js globals for one reason or another. This commonly happens in Jest because it intentionally robs you of Node.js globals and fails to re-add them in their entirely. As the result, you have to explicitly add them yourself.
Create a jest.polyfills.js
file next to your jest.config.js
with the following content:
// jest.polyfills.js
/**
* @note The block below contains polyfills for Node.js globals
* required for Jest to function when running JSDOM tests.
* These HAVE to be require's and HAVE to be in this exact
* order, since "undici" depends on the "TextEncoder" global API.
*
* Consider migrating to a more modern test runner if
* you don't want to deal with this.
*/
const { TextDecoder, TextEncoder } = require('node:util')
Object.defineProperties(globalThis, {
TextDecoder: { value: TextDecoder },
TextEncoder: { value: TextEncoder },
})
const { Blob, File } = require('node:buffer')
const { fetch, Headers, FormData, Request, Response } = require('undici')
Object.defineProperties(globalThis, {
fetch: { value: fetch, writable: true },
Blob: { value: Blob },
File: { value: File },
Headers: { value: Headers },
FormData: { value: FormData },
Request: { value: Request },
Response: { value: Response },
})
Make sure to install
undici
. It’s the official fetch implementation in Node.js.
Then, set the setupFiles
option in jest.config.js
to point to the newly created jest.polyfills.js
:
// jest.config.js
module.exports = {
setupFiles: ['./jest.polyfills.js'],
}
Pay attention it’s the setupFiles
option, and not setupFilesAfterEnv
.
The missing Node.js globals must be injected before the environment (e.g.
JSDOM).
If you find this setup cumbersome, consider migrating to a modern testing framework, like Vitest, which has none of the Node.js globals issues and provides native ESM support out of the box.
Mock responses don’t arrive at tests
HTTP requests have asynchronous nature. When testing code that depends on the resolution of those requests, like a UI element that renders once the response is received, you need to account for that asynchronicity. This often means using the right tools of your testing framework to properly await UI elements.
// test/suite.test.ts
import { render, screen } from '@testing-library/react'
import { Welcome } from '../components/Welcome'
it('renders the welcome text', async () => {
render(<Welcome />)
// Make sure to use "findBy*" methods that will attempt
// to look up an element multiple times before throwing.
// You can also use "waitFor" as an alternative.
await screen.findByText('Hello, Jack')
})
Use your testing framework’s recommended way of awaiting asynchronous code
Do not introduce arbitrary setTimeout
/sleep
functions because they subject your tests to race conditions! The only reliable way to await asynchronous code is to await the state that derives from it (i.e. that a certain UI element has appeared in the DOM).
Receiving stale mock responses
Modern request libraries, like SWR, React Query, or Apollo, often introduce cache to guarantee great user experience and optimal runtime performance. Note that caching is not automatically disabled while testing, which may lead to your tests receiving stale/wrong data across different test suites.
Please refer to your request library’s documentation on how to correctly disable cache in tests.
For example, here’s how to disable cache using SWR:
// test/suite.test.ts
import { cache } from 'swr'
beforeEach(() => {
// Reset the cache before each new test so there are
// no stale responses when requesting same endpoints.
cache.clear()
})
Requests are not resolving when using jest.useFakeTimers
When using fake timers in Jest, all timer APIs are mocked, including queueMicrotask
. The queueMicrotask
API is used internally by the global fetch in Node.js to parse request/response bodies. Because of this, when using jest.useFakeTimers()
with the default configuration, body reading methods like await request.text()
and await request.json()
will not resolve properly.
queueMicrotask
For example, here’s how to prevent Jest from faking the queueMicrotask
calls when using jest.useFakeTimers()
:
jest.useFakeTimers({
// Explicitly tell Jest not to affect the "queueMicrotask" calls.
doNotFake: ['queueMicrotask'],
})
Please refer to Jest’s documentation on fake timers.
RTK Query requests are not intercepted
A common mistake when using RTK Query is not setting the baseUrl
in the baseQuery
configuration. Without this, the requests will have relative URLs, which is a no-op in Node.js (see this issue for more details).
baseUrl
in your baseQuery
:createApi({
baseQuery: fetchBaseQuery({
baseUrl: new URL('/your/api/endpoint', location.origin).href,
}),
})