VOOZH about

URL: https://dev.to/0012303/msw-has-a-free-api-mocking-library-that-works-everywhere-1nfb

⇱ MSW Has a Free API Mocking Library That Works Everywhere - DEV Community


Mocking APIs in tests is painful. MSW (Mock Service Worker) intercepts requests at the network level — same mocks work in tests, Storybook, and development.

The Problem

// Traditional mocking — tightly coupled, breaks easily
jest.mock("../api/users", () => ({ getUsers: jest.fn().mockResolvedValue([]) }));

// What if the component uses fetch directly?
// What if it goes through axios?
// What if the URL changes?

MSW Solution: Mock the Network

// mocks/handlers.ts
import { http, HttpResponse } from "msw";

export const handlers = [
 // GET /api/users
 http.get("/api/users", () => {
 return HttpResponse.json([
 { id: 1, name: "Alice", email: "alice@test.com" },
 { id: 2, name: "Bob", email: "bob@test.com" },
 ]);
 }),

 // POST /api/users
 http.post("/api/users", async ({ request }) => {
 const body = await request.json();
 return HttpResponse.json({ id: 3, ...body }, { status: 201 });
 }),

 // Error responses
 http.get("/api/users/:id", ({ params }) => {
 if (params.id === "999") {
 return HttpResponse.json({ message: "Not found" }, { status: 404 });
 }
 return HttpResponse.json({ id: params.id, name: "User" });
 }),
];

These handlers work regardless of whether your code uses fetch, axios, ky, or any other HTTP client.

In Tests (Vitest/Jest)

// mocks/server.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";

export const server = setupServer(...handlers);

// setup.ts
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
// UserList.test.tsx
import { render, screen } from "@testing-library/react";
import { server } from "../mocks/server";
import { http, HttpResponse } from "msw";

test("shows users", async () => {
 render(<UserList />);
 expect(await screen.findByText("Alice")).toBeInTheDocument();
 expect(screen.getByText("Bob")).toBeInTheDocument();
});

test("handles error", async () => {
 // Override handler for this test only
 server.use(
 http.get("/api/users", () => {
 return HttpResponse.json(null, { status: 500 });
 })
 );
 render(<UserList />);
 expect(await screen.findByText("Failed to load users")).toBeInTheDocument();
});

In Browser (Development)

// 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();
}

Your app runs with mocked APIs — build frontend before backend is ready.

In Storybook

import { http, HttpResponse } from "msw";

export const WithUsers: Story = {
 parameters: {
 msw: {
 handlers: [
 http.get("/api/users", () =>
 HttpResponse.json([{ id: 1, name: "Storybook User" }])
 ),
 ],
 },
 },
};

Advanced Features

Network-Level Assertions

const createUser = http.post("/api/users", async ({ request }) => {
 const body = await request.json();
 // MSW 2.0: passthrough to real API in specific cases
 if (body.email.endsWith("@real.com")) {
 return passthrough();
 }
 return HttpResponse.json({ id: 1, ...body });
});

Streaming Responses

http.get("/api/stream", () => {
 const stream = new ReadableStream({ ... });
 return new HttpResponse(stream, {
 headers: { "Content-Type": "text/event-stream" },
 });
});

Building APIs or need robust testing infrastructure? I create developer tools and data solutions. Email spinov001@gmail.com or explore my Apify tools.