Mock Network Requests with MSW
How to use Mock Service Worker (MSW) to intercept network requests in JavaScript tests and development, including REST and GraphQL mocking.
Note: This guide follows English-language naming conventions and terminology standards common in international development teams. Examples use English identifiers and comments to maximize compatibility across codebases and tooling.
Overview
Mock Service Worker (MSW) intercepts network requests at the Service Worker level in browsers and at the fetch/http level in Node.js. Instead of patching modules or starting a mock server, MSW declares request handlers that return mock responses. The same handlers work in development, tests, and Storybook.
When to Use
- Mocking API responses during frontend development without a backend
- Testing components that fetch data with
fetch,axios, or@tanstack/react-query - Testing GraphQL queries and mutations without a running GraphQL server
- Sharing mocks between Storybook, unit tests, and integration tests
- Developing frontend features before the backend API is ready
When NOT to Use
- Testing your own backend API — use
supertestorWebTestClient - Testing real network behavior — MSW intercepts before the network layer
- Load testing — MSW adds interception overhead
- Testing WebSocket connections — MSW 2.x supports it, but it’s less mature than HTTP mocking
Solution
Setup
npm install -D msw
Basic REST handlers
import { http, HttpResponse } from "msw";
export const handlers = [
http.get("https://api.example.com/users/:id", ({ params }) => {
return HttpResponse.json({
id: Number(params.id),
name: "Alice",
email: "alice@example.com",
});
}),
http.post("https://api.example.com/users", async ({ request }) => {
const body = await request.json();
return HttpResponse.json(
{ id: 42, ...body },
{ status: 201 },
);
}),
http.get("https://api.example.com/users", ({ request }) => {
const url = new URL(request.url);
const page = url.searchParams.get("page") || "1";
return HttpResponse.json({
data: [{ id: 1, name: "Alice" }, { id: 2, name: "Bob" }],
page: Number(page),
total: 2,
});
}),
];
Using MSW in Node.js tests (Vitest)
import { setupServer } from "msw/node";
import { afterAll, afterEach, beforeAll, describe, it, expect } from "vitest";
import { handlers } from "./handlers";
const server = setupServer(...handlers);
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
describe("User API", () => {
it("fetches a user by ID", async () => {
const response = await fetch("https://api.example.com/users/1");
const user = await response.json();
expect(user.id).toBe(1);
expect(user.name).toBe("Alice");
});
it("creates a user", async () => {
const response = await fetch("https://api.example.com/users", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ name: "Charlie", email: "charlie@example.com" }),
});
expect(response.status).toBe(201);
const user = await response.json();
expect(user.id).toBe(42);
expect(user.name).toBe("Charlie");
});
});
GraphQL handlers
import { graphql, HttpResponse } from "msw";
export const graphqlHandlers = [
graphql.query("GetUser", ({ variables }) => {
return HttpResponse.json({
data: {
user: {
id: variables.id,
name: "Alice",
email: "alice@example.com",
},
},
});
}),
graphql.mutation("CreateUser", ({ variables }) => {
return HttpResponse.json({
data: {
createUser: {
id: 99,
...variables.input,
},
},
});
}),
];
Simulating errors
http.get("https://api.example.com/users/1", () => {
return HttpResponse.json(
{ error: "Not found" },
{ status: 404 },
);
});
http.get("https://api.example.com/server-error", () => {
return HttpResponse.json(
{ error: "Internal Server Error" },
{ status: 500 },
);
});
Simulating network errors
import { http, HttpResponse, delay } from "msw";
http.get("https://api.example.com/slow", async () => {
await delay(5000);
return HttpResponse.json({ data: "delayed" });
});
http.get("https://api.example.com/network-error", () => {
return HttpResponse.error();
});
Overriding handlers per test
it("handles 500 error", async () => {
server.use(
http.get("https://api.example.com/users/1", () => {
return HttpResponse.json({ error: "Server error" }, { status: 500 });
}),
);
const response = await fetch("https://api.example.com/users/1");
expect(response.status).toBe(500);
});
Using MSW in the browser (development)
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";
const worker = setupWorker(...handlers);
await worker.start({
onUnhandledRequest: "bypass",
});
console.log("MSW worker started");
Generate the service worker file:
npx msw init public/ --save
Verifying requests
import { http, HttpResponse } from "msw";
let lastRequestBody: unknown;
export const captureHandler = http.post(
"https://api.example.com/orders",
async ({ request }) => {
lastRequestBody = await request.json();
return HttpResponse.json({ id: 1 }, { status: 201 });
},
);
// In test
it("sends correct order payload", async () => {
await submitOrder({ productId: 10, quantity: 3 });
expect(lastRequestBody).toEqual({ productId: 10, quantity: 3 });
});
Variants
Using MSW with React Testing Library
import { render, screen, waitFor } from "@testing-library/react";
import { UserProfile } from "./UserProfile";
it("displays user data from API", async () => {
server.use(
http.get("/api/users/1", () => {
return HttpResponse.json({ name: "Alice", email: "alice@example.com" });
}),
);
render(<UserProfile userId={1} />);
await waitFor(() => {
expect(screen.getByText("Alice")).toBeInTheDocument();
});
});
Using MSW with Storybook
// .storybook/preview.tsx
import { setupWorker } from "msw/browser";
import { handlers } from "../src/mocks/handlers";
const worker = setupWorker(...handlers);
await worker.start();
export const parameters = {
msw: { handlers },
};
Best Practices
-
For a deeper guide, see Stub External HTTP Services with WireMock.
-
Share handlers between tests, Storybook, and development — one source of truth for mock data
-
Use
server.resetHandlers()inafterEachto clear per-test overrides -
Set
onUnhandledRequest: "error"in tests to catch unmocked requests -
Use
delay()to test loading states and timeout handling -
Keep handlers in a separate
handlers.tsfile for reusability -
Use
server.use()for one-off overrides instead of modifying shared handlers
Common Mistakes
- Not calling
server.listen()inbeforeAll: MSW won’t intercept requests until the server starts. - Not resetting handlers: per-test overrides leak into subsequent tests. Always call
resetHandlers(). - Mocking relative URLs in Node.js tests: use absolute URLs or configure a base URL. MSW in Node.js needs full URLs.
- Forgetting
awaitin async handlers:HttpResponse.json()is synchronous, but reading the request body withrequest.json()is async. - Not closing the server: call
server.close()inafterAllto prevent the process from hanging.
FAQ
How do I mock file uploads?
http.post("/api/upload", ({ request }) => {
const formData = await request.formData();
const file = formData.get("file") as File;
return HttpResponse.json({ filename: file.name, size: file.size });
});
Can I use MSW with axios?
Yes. MSW intercepts at the fetch and http level, which axios uses internally. No configuration needed.
How do I mock paginated responses?
http.get("/api/users", ({ request }) => {
const url = new URL(request.url);
const page = Number(url.searchParams.get("page") || 1);
const allUsers = Array.from({ length: 50 }, (_, i) => ({ id: i + 1 }));
const perPage = 10;
const start = (page - 1) * perPage;
return HttpResponse.json({
data: allUsers.slice(start, start + perPage),
page,
total: 50,
});
});
Does MSW work with WebSocket?
MSW 2.x supports WebSocket mocking via ws handler:
import { ws } from "msw";
const chat = ws.link("wss://api.example.com/chat");
export const handlers = [
chat.addEventListener("connection", ({ client }) => {
client.send("Welcome!");
}),
];
How do I debug unmocked requests?
Set onUnhandledRequest: "warn" to see warnings in the console, or "error" to fail tests on unmocked requests. Check the console output for the full URL and method.
Related Resources
Vitest Snapshot Testing for React
How to use Vitest snapshot testing to catch unintended UI changes in React components, including inline snapshots and snapshot updating workflows.
RecipeTest Express APIs with supertest
How to test Express.js REST API endpoints end-to-end using supertest, including status codes, JSON bodies, headers, authentication, and error handling.
RecipeAPI Mocking for Testing
Build reliable tests by mocking external APIs with WireMock, MockServer, and MSW to eliminate flakiness and test edge cases.