MSW (Mock Service Worker)
An API mocking library that intercepts network requests at the Service Worker level in browsers and via a Node.js interception layer in tests. Define request handlers once and use them across your browser dev environment, Jest, and Vitest test suites — no application code changes needed.
What is MSW?
Mock Service Worker is an API mocking library that sits at the network layer rather than patching your HTTP client. In the browser, it registers a Service Worker that intercepts outgoing requests before they leave the browser tab. In Node.js environments (Jest, Vitest, server-side rendering), it uses a lower-level HTTP interception layer with the same API.
The key advantage is that your application code is unaware of the mocking. Whether your app uses fetch, axios, ky, or any other library, MSW handlers intercept the requests transparently. This makes MSW mocks more realistic — the app goes through the same code path as it would with a real API.
Key Features
- Network-level interception — intercepts at the Service Worker or Node.js HTTP level, not by patching fetch/axios
- Shared handlers — define handlers once in a handlers.ts file; use the same handlers in browser dev and Node.js tests
- REST and GraphQL — first-class support for both REST (http.get, http.post, etc.) and GraphQL (graphql.query, graphql.mutation)
- One-time overrides — use server.use() inside a test to override a handler for that test only; automatically cleaned up after
- Request passthrough — use passthrough() to let certain requests reach the real API while mocking others
- Response streaming — simulate streamed responses (e.g., SSE, chunked transfers) with ReadableStream bodies
- TypeScript native — full TypeScript support for request body types, route params, and response types
Installation
# Install MSW npm install msw --save-dev # Initialize the Service Worker file for browser usage npx msw init public/ --save # The above copies mockServiceWorker.js into your public directory # Add "workerDirectory": "public" to package.json msw config if needed
Defining REST Handlers
Define handlers using http.get(), http.post(), and other HTTP method helpers. Return HttpResponse.json() for JSON responses or construct a custom HttpResponse for other status codes.
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
// GET /api/users — return a list of users
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'Alice', email: '[email protected]' },
{ id: 2, name: 'Bob', email: '[email protected]' },
]);
}),
// POST /api/users — create a user and echo it back
http.post('/api/users', async ({ request }) => {
const body = await request.json() as { name: string; email: string };
return HttpResponse.json(
{ id: Date.now(), ...body },
{ status: 201 }
);
}),
// Simulate a 404 for a missing resource
http.get('/api/users/:id', ({ params }) => {
if (Number(params.id) > 100) {
return new HttpResponse(null, { status: 404 });
}
return HttpResponse.json({ id: params.id, name: 'Alice' });
}),
]; Browser Setup
Set up the Service Worker with setupWorker() from msw/browser. Start it conditionally in development so it never activates in production builds.
// src/mocks/browser.ts — browser setup
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
// src/main.tsx (or index.tsx) — start the worker in development
async function enableMocking() {
if (process.env.NODE_ENV !== 'development') return;
const { worker } = await import('./mocks/browser');
// Start the Service Worker; onUnhandledRequest warns about missing handlers
return worker.start({ onUnhandledRequest: 'warn' });
}
enableMocking().then(() => {
ReactDOM.createRoot(document.getElementById('root')!).render( );
}); Node.js Setup (Tests)
For tests, use setupServer() from msw/node. Start, reset, and stop the server in your test setup file. Call server.resetHandlers() after each test to remove any per-test overrides.
// src/mocks/node.ts — Node.js setup (for tests)
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
// vitest.setup.ts or jest.setup.ts
import { server } from './src/mocks/node';
import { afterAll, afterEach, beforeAll } from 'vitest';
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers()); // reset overrides between tests
afterAll(() => server.close()); One-Time Handler Overrides
Use server.use() inside a test to temporarily override a handler for that test only. This is the standard pattern for testing error states, empty responses, and edge cases.
// One-time handler override inside a specific test
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/node';
import { render, screen, waitFor } from '@testing-library/react';
import UserList from './UserList';
test('shows an error when the API fails', async () => {
// Override for this test only — resets after the test via afterEach
server.use(
http.get('/api/users', () => {
return new HttpResponse(null, { status: 500 });
})
);
render( );
await waitFor(() => {
expect(screen.getByText('Failed to load users')).toBeInTheDocument();
});
}); GraphQL Handlers
Import graphql from msw and define handlers by operation name. Works for any GraphQL client — Apollo, urql, or raw fetch.
// GraphQL handler with MSW
import { graphql, HttpResponse } from 'msw';
export const graphqlHandlers = [
graphql.query('GetUser', ({ variables }) => {
return HttpResponse.json({
data: {
user: {
id: variables.id,
name: 'Alice',
email: '[email protected]',
},
},
});
}),
graphql.mutation('CreateUser', ({ variables }) => {
return HttpResponse.json({
data: {
createUser: { id: '99', ...variables.input },
},
});
}),
]; Pros and Cons
Pros
- ✓ Works at network level — agnostic to HTTP client library
- ✓ Same handlers used in browser dev and Node.js tests
- ✓ Realistic mock behavior — app runs the same code path
- ✓ First-class TypeScript support
- ✓ One-time overrides per test without global state pollution
Cons
- ✗ Requires serving mockServiceWorker.js from your public directory
- ✗ Service Worker requires HTTPS or localhost (no arbitrary domains)
- ✗ v2 API has breaking changes from v1 — migration required for older codebases
- ✗ Not suitable for mocking WebSocket connections (use a different tool)
- ✗ Slight overhead in test setup compared to in-module mocking
Frequently Asked Questions
What is Mock Service Worker (MSW)?▼
Mock Service Worker is an API mocking library that intercepts outgoing network requests using a Service Worker (in the browser) or a Node.js interceptor layer (in tests). It lets you define request handlers that return mock responses, without modifying your application code or using a proxy server. This means your app code runs unchanged — MSW intercepts at the network layer.
How does MSW differ from mocking fetch or axios directly?▼
Direct mocking (jest.mock('axios') or vi.spyOn(window, 'fetch')) replaces the underlying HTTP function with a mock, which ties your test to the implementation detail of which HTTP library is used. MSW intercepts at the network level, so it works regardless of whether your app uses fetch, axios, ky, or any other client. Switching your app from fetch to axios does not break any MSW handlers.
Do I need to configure anything different for browser vs Node.js?▼
Yes. For browsers, MSW uses a Service Worker (mockServiceWorker.js) that you serve from your public directory. Initialize it with npx msw init public/ and start it with worker.start() in development. For Node.js (tests, SSR), MSW uses a request interception layer without a Service Worker. Set up a server with setupServer() from msw/node and start it in your test setup file. Both use the same handlers.
How do I handle one-time response overrides in tests?▼
Use server.use() inside a test to add a temporary handler that takes priority over the default handlers. This override is active only for that test — the afterEach(() => server.resetHandlers()) call in your setup file removes it after each test, restoring the default handlers. This pattern is ideal for testing error states, empty states, or edge cases without changing the default mock data.
Does MSW support GraphQL?▼
Yes. Import the graphql object from msw and define handlers with graphql.query() and graphql.mutation(). MSW matches GraphQL requests by operation name (extracted from the request body). Response shape follows the standard GraphQL format with a data key. This works for both REST-over-HTTP GraphQL endpoints and Apollo Client setups.
Can MSW be used in production or only in development and tests?▼
MSW is primarily a development and testing tool. The recommended pattern is to conditionally start the Service Worker only when NODE_ENV is 'development', so it never activates in production builds. In test environments, the Node.js server is started in setup files and torn down after all tests complete. Running MSW in production would intercept real API calls, which is almost never desired.
Related Products
on GumroadAPI Testing Cheatsheet Pack
Quick-reference cheatsheets for REST, GraphQL, and WebSocket testing. Includes Postman collections, shell scripts for smoke tests and load tests, and JWT decoder.
Buy on Gumroad →Developer Productivity Bundle
Stop rebuilding the same setup every project. 51 VSCode snippets, 40 Git aliases, 5 Docker Compose stacks, 5 GitHub Actions workflows, 5 README templates, and a dotfiles kit. Set up a professional dev environment in under an hour.
Buy on Gumroad →AI Prompt Engineering Toolkit
58 structured prompt templates for Claude, ChatGPT, and Gemini — organized by use case. Code Review, Debugging, Documentation, Architecture, and 10 model-specific tips. Stop writing prompts from scratch.
Buy on Gumroad →You Might Also Need
Vitest Configuration Generator
Generate a complete vitest.config.ts visually. Configure test environment (node/jsdom/happy-dom), coverage provider, reporters, thresholds, and path aliases.
Open free tool →
GraphQL Query Builder
Build GraphQL queries visually from your schema SDL. Select root fields, pick sub-fields, add arguments, and generate query/mutation/subscription strings instantly. Supports nested types.
Open free tool →
OpenAPI Validator
Paste your OpenAPI 3.x spec (YAML or JSON) to validate structure, find missing required fields, detect duplicate paths, check for missing operationIds, and get errors and warnings with path references.
Open free tool →
More Free Tools
Related Articles
MSW 2.0: The Best Way to Mock APIs in Frontend Tests
A complete guide to Mock Service Worker (MSW) 2.0 — setup, request handlers, Vitest and Jest integration, Storybook mocking, and best practices for realistic API mocking in frontend tests.
Modern JavaScript Testing 2026: Vitest vs Jest vs Playwright
Complete guide to JavaScript testing in 2026. Compare Vitest vs Jest for unit tests, Playwright vs Cypress for E2E, component testing, coverage tools, and CI/CD integration.
Vitest: The Fast Unit Testing Framework for Vite Projects - 2026 Guide
Complete guide to Vitest in 2026: setup, configuration, mocking, coverage, watch mode, and a detailed comparison with Jest to help you decide which testing framework fits your project.
Vitest vs Jest vs Mocha: Complete Testing Framework Comparison 2026
A comprehensive comparison of Vitest, Jest, and Mocha for JavaScript and TypeScript projects in 2026. Covers speed benchmarks, configuration, TypeScript support, mocking, code coverage, and migration strategies.
Vitest Unit Testing: Complete Setup Guide for 2026
Learn Vitest from zero to production: installation, test syntax, mocking, coverage reports, Vitest UI, and Vitest vs Jest migration. Practical guide for Vite and non-Vite projects.
Enjoying this tool? Get more like it.
Weekly developer tool guides, productivity playbooks & AI tricks. Free. No spam.
Unlock AI-Powered Dev Tools
- ⚡ AI Code Review, Doc Generator & SQL Builder
- ⚡ All premium templates & early access
- ⚡ Member discounts on Gumroad products
$9/mo after trial · Cancel anytime
Want the full toolkit?
Get DevPlaybook Pro
Every template, guide, boilerplate, and automation script in one bundle. 13 premium products — grab them all at once and save big.
MIT licensed · Instant download · No subscription
See what's included → Browse all deals →