What is MSW?
MSW (Mock Service Worker) is a framework-agnostic library that intercepts network requests at the network level, providing mock responses. This means it doesn’t care whether you use fetch, Axios, GraphQL, etc.—MSW works regardless.
It achieves this using:
- A Service Worker in the browser
- A Node-based interceptor during testing
This ensures the mock lives closer to the actual request, mimicking real-world behavior without patching individual libraries.
How Does MSW Work?
MSW intercepts API calls at the network layer. This means:
If a mock exists for an API endpoint, MSW will return the mocked response.
If no mock is defined, the request proceeds to the real API.
Environment | MSW Mode | How it Intercepts |
---|---|---|
Browser | Service Worker | Runs a real service worker that intercepts requests from the browser in network layer |
Node.js | Node Interceptor | Uses node-request-interceptor to hook into HTTP/HTTPS in process level |
Why MSW ??
True Isolation
Mocks network at lowest possible level - no need to modify application code
Framework Agnostic Tests
Works with React Testing Library, Vue Test Utils, Svelte Testing Library, etc.
Realistic Behavior
Simulates actual network conditions (latency, errors, headers) unlike jest.mock()
Cross-Library Support
Intercepts requests from ANY HTTP client automatically
MSW in the Browser
To set up MSW in the browser for development:
- Install the Service Worker
Run the following command to install the service worker file:
npx msw init public/
This will add the mockServiceWorker.js file to your public/ directory.
- Create your mock handlers Create a handler file with your REST or GraphQL mocks:
import {
http, HttpResponse,
} from "msw";
export const handlers = [
http.get(`/api/whoami`, () => {
return HttpResponse.json({
status: 200,
data: {
user: "Jack Sparrow"
},
});
}),
]
- Set up broswer worker Create a browser.ts file to initialize the MSW browser worker:
import { setupWorker } from 'msw';
import { handlers } from './handlers'
export const worker = setupWorker(...handlers);
I also created a initMock file to start initiate the mock worker
- Start the worker Instead of starting the worker in Rootlayout I prefer to do it by creating a Wrapper component.
'use client';
import { useEffect, useState } from 'react';
export function StartMockWorker({children}: {children: React.ReactNode}) {
const [isMockReady, setMockReady] = useState(false);
useEffect(() => {
async function enableMocks() {
if (process.env.NODE_ENV === 'development') {
const { initMocks } = await import('@/common/mock/initmock');
await initMocks();
}
setMockReady(true);
}
enableMocks();
}, []);
if (!isMockReady) {
return <div>Loading mocks...</div>;
}
return <>{children}</> ;
}
MSW with Jest for Testing
Jest runs tests in a Node.js environment, not a real browser. This means:
No Browser APIs Exist
Service Workers (which power MSW in browsers) are a browser-exclusive feature - they don't exist in Node.js.
JSDOM Isn't a Real Browser
While Jest uses JSDOM to simulate a browser, it's still just a Node.js process. Critical browser features like Service Workers aren't implemented.
Setup
- Create a server
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
- In the jest setup file
// jest.setup.ts
import '@testing-library/jest-dom';
import { server } from '@/common/mock/server';
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
It ensures that your tests run with network requests intercepted and mocked, and that the state is cleaned up between tests.
Polyfills Required for Node + JSDOM
When running in jsdom with Jest, you might run into missing globals like:
Blob
. TextEncoder
,BroadcastChannel
(added in MSW v2+), MessagePort
etc
You’ll need to manually polyfill these in your Jest setup file
/* eslint-disable @typescript-eslint/no-require-imports */
// jest.polyfills.js
const {
TextDecoder, TextEncoder,
} = require('node:util');
const { ReadableStream, TransformStream } = require('node:stream/web');
const { BroadcastChannel, MessagePort } = require("node:worker_threads")
Object.defineProperties(globalThis, {
TextDecoder: { value: TextDecoder },
TextEncoder: { value: TextEncoder },
ReadableStream: { value: ReadableStream },
TransformStream: { value: TransformStream },
BroadcastChannel: { value: BroadcastChannel },
MessagePort:{value:MessagePort}
})
const {
Blob, File,
} = require('node:buffer')
const {
fetch, Headers, FormData, Request, Response,
} = require('undici')
Object.assign(globalThis, {
fetch,
Headers,
FormData,
Request,
Response,
Blob,
File,
})
Conclusion
MSW is powerful, developer-friendly, and works seamlessly in both the browser and Node environments:
- Great for mocking real-world APIs in local dev.
- Essential for writing fast, reliable, and network-independent Jest tests.
- Framework-agnostic and works with any HTTP client.
With proper setup and polyfills, it becomes your best friend in test and dev workflows.
Full Setup
You can find the complete setup, including all code examples and configurations, in this GitHub repo:
👉 msw-with-next
Top comments (2)
Finally get the SSE handler done in
src/common/mock/handler.ts
Many thanks for your detailed explanation on how to use MSW in Next.js
Based on your example I tried to mock SSE but fetch always failed.
MSW does not support mocking event stream?