DEV Community

Alex Spinov
Alex Spinov

Posted on

MSW Has a Free API That Makes API Mocking in Tests and Dev Actually Work

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 });
  }),
];
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

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();
}
Enter fullscreen mode Exit fullscreen mode

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);
});
Enter fullscreen mode Exit fullscreen mode

Passthrough: Let Some Requests Through

import { http, passthrough } from "msw";

http.get("/api/analytics/*", () => {
  return passthrough(); // Don't mock analytics
});
Enter fullscreen mode Exit fullscreen mode

Testing scraping integrations? My Apify tools + MSW = reliable test suites for data pipelines.

Custom testing solution? Email spinov001@gmail.com

Top comments (0)