Your frontend depends on 5 APIs. Backend isn't ready. You create mockData.json, wrap fetch calls in if (process.env.NODE_ENV === 'development'), and your production code becomes littered with mock logic.
What if you could intercept network requests at the service worker level — without touching your application code?
That's MSW (Mock Service Worker). Your app makes real fetch calls. MSW intercepts them and returns mock responses. Your code doesn't know the difference.
Quick Start
npm install msw --save-dev
npx msw init ./public # For browser usage
Define Handlers
// mocks/handlers.ts
import { http, HttpResponse } from "msw";
export const handlers = [
http.get("/api/users", () => {
return HttpResponse.json([
{ id: "1", name: "Aleksej", email: "dev@example.com" },
{ id: "2", name: "Maria", email: "maria@example.com" },
]);
}),
http.post("/api/users", async ({ request }) => {
const body = await request.json();
return HttpResponse.json(
{ id: "3", ...body },
{ status: 201 }
);
}),
http.get("/api/users/:id", ({ params }) => {
const { id } = params;
if (id === "999") {
return HttpResponse.json(
{ error: "User not found" },
{ status: 404 }
);
}
return HttpResponse.json({ id, name: "Aleksej" });
}),
http.delete("/api/users/:id", () => {
return new HttpResponse(null, { status: 204 });
}),
];
Browser Setup (Development)
// mocks/browser.ts
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
// main.tsx
async function enableMocking() {
if (process.env.NODE_ENV !== "development") return;
const { worker } = await import("./mocks/browser");
return worker.start();
}
enableMocking().then(() => {
ReactDOM.createRoot(document.getElementById("root")!).render(<App />);
});
Your app's fetch calls work normally. MSW intercepts them at the network level. Open DevTools Network tab — you'll see the requests as if hitting a real API.
Node.js Setup (Testing)
// mocks/server.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);
// vitest.setup.ts
import { beforeAll, afterEach, afterAll } from "vitest";
import { server } from "./mocks/server";
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// tests/users.test.ts
import { http, HttpResponse } from "msw";
import { server } from "../mocks/server";
test("handles server error", async () => {
// Override handler for this test only
server.use(
http.get("/api/users", () => {
return HttpResponse.json({ error: "Server Error" }, { status: 500 });
})
);
render(<UserList />);
await expect(screen.findByText("Failed to load users")).resolves.toBeInTheDocument();
});
GraphQL Mocking
import { graphql, HttpResponse } from "msw";
export const graphqlHandlers = [
graphql.query("GetUser", ({ variables }) => {
return HttpResponse.json({
data: {
user: { id: variables.id, name: "Aleksej", email: "dev@test.com" },
},
});
}),
graphql.mutation("CreateUser", ({ variables }) => {
return HttpResponse.json({
data: {
createUser: { id: "new-id", ...variables.input },
},
});
}),
];
MSW vs Other Mocking
| Feature | MSW | nock | json-server | Mirage |
|---|---|---|---|---|
| Browser support | Yes | No | Separate process | Yes |
| Node.js support | Yes | Yes | Separate process | No |
| Network-level | Service Worker | HTTP interceptor | Real server | Pretender |
| Code changes needed | None | None | None | Route setup |
| GraphQL support | Yes | Manual | No | No |
When to Choose MSW
Choose MSW when:
- Frontend development while APIs aren't ready
- Testing components that make API calls
- You want consistent mocks across browser and Node.js
- You need to test error states, loading states, edge cases
Skip MSW when:
- You only need simple test data (just use fixtures)
- Backend is always available and stable
- You're testing backend code (use real DB or test containers)
Start here: mswjs.io
Need custom data extraction, scraping, or automation? I build tools that collect and process data at scale — 78 actors on Apify Store and 265+ open-source repos. Email me: Spinov001@gmail.com | My Apify Actors
Top comments (0)