Mock Service Worker (MSW) intercepts network requests at the service worker level. No monkey-patching fetch. No test-specific HTTP clients. Your code doesn't know it's being mocked.
Setup: Define Handlers
import { http, HttpResponse } from "msw";
export const handlers = [
// GET request
http.get("/api/products", () => {
return HttpResponse.json([
{ id: 1, title: "Widget", price: 29.99 },
{ id: 2, title: "Gadget", price: 49.99 },
]);
}),
// POST with request body
http.post("/api/products", async ({ request }) => {
const body = await request.json();
return HttpResponse.json({ id: 3, ...body }, { status: 201 });
}),
// Dynamic params
http.get("/api/products/:id", ({ params }) => {
return HttpResponse.json({ id: params.id, title: "Product" });
}),
// Query parameters
http.get("/api/search", ({ request }) => {
const url = new URL(request.url);
const query = url.searchParams.get("q");
return HttpResponse.json({ results: [], query });
}),
// Error responses
http.delete("/api/products/:id", () => {
return new HttpResponse(null, { status: 204 });
}),
];
Testing: Vitest/Jest Integration
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
const server = setupServer(...handlers);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
test("fetches products", async () => {
const res = await fetch("/api/products");
const data = await res.json();
expect(data).toHaveLength(2);
expect(data[0].title).toBe("Widget");
});
test("handles errors", async () => {
// Override for this test only
server.use(
http.get("/api/products", () => {
return HttpResponse.json({ error: "Server Error" }, { status: 500 });
})
);
const res = await fetch("/api/products");
expect(res.status).toBe(500);
});
Browser: Development Mocking
// src/mocks/browser.ts
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
// main.tsx
if (process.env.NODE_ENV === "development") {
const { worker } = await import("./mocks/browser");
await worker.start();
}
Network Behaviors
import { http, HttpResponse, delay } from "msw";
// Simulate slow network
http.get("/api/slow", async () => {
await delay(3000);
return HttpResponse.json({ data: "finally" });
});
// Simulate network error
http.get("/api/broken", () => {
return HttpResponse.error();
});
// Streaming response
http.get("/api/stream", () => {
const stream = new ReadableStream({
async start(controller) {
controller.enqueue(new TextEncoder().encode("chunk 1\n"));
await delay(100);
controller.enqueue(new TextEncoder().encode("chunk 2\n"));
controller.close();
},
});
return new HttpResponse(stream);
});
Passthrough: Let Some Requests Through
import { http, passthrough } from "msw";
http.get("/api/analytics/*", () => {
return passthrough(); // Don't mock analytics
});
Testing scraping integrations? My Apify tools + MSW = reliable test suites for data pipelines.
Custom testing solution? Email spinov001@gmail.com
Top comments (0)