API Mocking
API mocking with MSW (Mock Service Worker) and Prism for development and testing
You are an expert in API mocking using MSW for browser and Node.js environments and Prism for OpenAPI-driven mock servers. ## Key Points - Use MSW for test and development mocking in JavaScript/TypeScript projects — it requires no server and works at the network level. - Use Prism when you have an OpenAPI spec and want a zero-code mock server, especially for cross-team or cross-language use. - Set `onUnhandledRequest: "error"` in MSW test setup to catch accidental unmocked requests immediately. - Use `server.resetHandlers()` in `afterEach` to prevent handler overrides from leaking between tests. - Provide realistic examples in your OpenAPI spec so Prism responses are useful for frontend development. - Keep mock handlers close to reality — if the real API returns paginated responses, your mocks should too. - Forgetting to call `server.listen()` in test setup, meaning MSW is not intercepting anything and tests hit real endpoints or fail. - Not resetting handlers between tests, causing one test's override to affect subsequent tests. - Using MSW mocks that diverge from the real API schema — mocks pass but real integration fails. Combine with contract tests or OpenAPI validation to keep them in sync. - Running `prism mock` without `--dynamic` and expecting varied data — static mode returns the same example every time. - Mocking too much in integration tests — mock external services, not the code under test. ## Quick Example ```bash npm install --save-dev msw # Initialize the service worker for browser use npx msw init public/ --save ``` ```bash npm install -g @stoplight/prism-cli ```
skilldb get api-testing-skills/API MockingFull skill: 274 linesAPI Mocking (MSW & Prism) — API Testing
You are an expert in API mocking using MSW for browser and Node.js environments and Prism for OpenAPI-driven mock servers.
Core Philosophy
Overview
API mocking enables frontend and backend teams to work in parallel by simulating API responses. MSW (Mock Service Worker) intercepts requests at the network level using service workers (browser) or custom interceptors (Node.js), making mocks transparent to application code. Prism generates a mock server directly from an OpenAPI specification, returning realistic responses with zero code.
Setup & Configuration
MSW installation
npm install --save-dev msw
# Initialize the service worker for browser use
npx msw init public/ --save
MSW project structure
src/
mocks/
handlers.ts # Request handlers
browser.ts # Browser worker setup
server.ts # Node.js server setup
fixtures/
users.json
Prism installation
npm install -g @stoplight/prism-cli
Core Patterns
MSW — Defining handlers
// src/mocks/handlers.ts
import { http, HttpResponse } from "msw";
const users = [
{ id: 1, name: "Alice", email: "alice@example.com" },
{ id: 2, name: "Bob", email: "bob@example.com" },
];
export const handlers = [
// GET list
http.get("/api/users", ({ request }) => {
const url = new URL(request.url);
const page = parseInt(url.searchParams.get("page") || "1");
const perPage = parseInt(url.searchParams.get("per_page") || "10");
const slice = users.slice((page - 1) * perPage, page * perPage);
return HttpResponse.json(slice, {
headers: { "X-Total-Count": String(users.length) },
});
}),
// GET by ID
http.get("/api/users/:id", ({ params }) => {
const user = users.find((u) => u.id === Number(params.id));
if (!user) {
return HttpResponse.json(
{ error: "User not found" },
{ status: 404 }
);
}
return HttpResponse.json(user);
}),
// POST create
http.post("/api/users", async ({ request }) => {
const body = await request.json();
const newUser = { id: users.length + 1, ...body };
users.push(newUser);
return HttpResponse.json(newUser, { status: 201 });
}),
// Simulate network error
http.get("/api/unstable", () => {
return HttpResponse.error();
}),
// Simulate slow response
http.get("/api/slow", async () => {
await new Promise((resolve) => setTimeout(resolve, 3000));
return HttpResponse.json({ status: "ok" });
}),
];
MSW — Browser setup
// src/mocks/browser.ts
import { setupWorker } from "msw/browser";
import { handlers } from "./handlers";
export const worker = setupWorker(...handlers);
// src/main.ts (or index.ts)
async function enableMocking() {
if (process.env.NODE_ENV !== "development") return;
const { worker } = await import("./mocks/browser");
return worker.start({ onUnhandledRequest: "bypass" });
}
enableMocking().then(() => {
// Mount your app
createRoot(document.getElementById("root")).render(<App />);
});
MSW — Node.js setup for tests
// src/mocks/server.ts
import { setupServer } from "msw/node";
import { handlers } from "./handlers";
export const server = setupServer(...handlers);
// test/setup.ts (Jest or Vitest global setup)
import { server } from "../src/mocks/server";
beforeAll(() => server.listen({ onUnhandledRequest: "error" }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
MSW — Per-test handler overrides
import { http, HttpResponse } from "msw";
import { server } from "../src/mocks/server";
test("shows error state when API fails", async () => {
// Override just for this test
server.use(
http.get("/api/users", () => {
return HttpResponse.json(
{ error: "Internal Server Error" },
{ status: 500 }
);
})
);
render(<UserList />);
expect(await screen.findByText(/something went wrong/i)).toBeInTheDocument();
});
Prism — Running a mock server from OpenAPI
# Start mock server from an OpenAPI spec
prism mock openapi.yaml
# With dynamic response generation (uses examples + faker)
prism mock openapi.yaml --dynamic
# Specific port
prism mock openapi.yaml --port 4010
# Validate requests against the spec (returns 422 on invalid requests)
prism mock openapi.yaml --errors
Prism — OpenAPI spec with examples
paths:
/users/{id}:
get:
operationId: getUser
parameters:
- name: id
in: path
required: true
schema:
type: integer
responses:
"200":
description: A user
content:
application/json:
schema:
$ref: "#/components/schemas/User"
example:
id: 42
name: Alice
email: alice@example.com
"404":
description: Not found
content:
application/json:
example:
error: User not found
components:
schemas:
User:
type: object
required: [id, name, email]
properties:
id:
type: integer
name:
type: string
email:
type: string
format: email
# Prism will serve the example response:
curl http://localhost:4010/users/42
# {"id": 42, "name": "Alice", "email": "alice@example.com"}
# Request a specific response code via Prefer header:
curl -H "Prefer: code=404" http://localhost:4010/users/42
# {"error": "User not found"}
Prism — Proxy mode (validation gateway)
# Proxy requests to real API, validate request/response against spec
prism proxy openapi.yaml https://api.example.com --errors
Best Practices
- Use MSW for test and development mocking in JavaScript/TypeScript projects — it requires no server and works at the network level.
- Use Prism when you have an OpenAPI spec and want a zero-code mock server, especially for cross-team or cross-language use.
- Set
onUnhandledRequest: "error"in MSW test setup to catch accidental unmocked requests immediately. - Use
server.resetHandlers()inafterEachto prevent handler overrides from leaking between tests. - Provide realistic examples in your OpenAPI spec so Prism responses are useful for frontend development.
- Keep mock handlers close to reality — if the real API returns paginated responses, your mocks should too.
Common Pitfalls
- Forgetting to call
server.listen()in test setup, meaning MSW is not intercepting anything and tests hit real endpoints or fail. - Not resetting handlers between tests, causing one test's override to affect subsequent tests.
- Using MSW mocks that diverge from the real API schema — mocks pass but real integration fails. Combine with contract tests or OpenAPI validation to keep them in sync.
- Running
prism mockwithout--dynamicand expecting varied data — static mode returns the same example every time. - Mocking too much in integration tests — mock external services, not the code under test.
Anti-Patterns
Over-engineering for hypothetical scale. Building for millions of users when you have hundreds adds complexity without value. Solve today's problems first.
Ignoring the existing ecosystem. Reinventing functionality that mature libraries already provide well wastes time and introduces unnecessary risk.
Premature abstraction. Creating elaborate frameworks and utilities before you have enough concrete cases to know what the abstraction should look like produces the wrong abstraction.
Neglecting error handling at boundaries. Internal code can trust its inputs, but system boundaries (user input, APIs, file I/O) require defensive validation.
Skipping documentation for obvious code. What is obvious to you today will not be obvious to your colleague next month or to you next year.
Install this skill directly: skilldb add api-testing-skills
Related Skills
Bruno
Bruno API client for git-friendly, offline-first API testing with Bru markup language
Contract Testing
Pact contract testing for consumer-driven API contracts between microservices
Httpie
HTTPie CLI for human-friendly API testing, scripting, and debugging from the terminal
Load Testing
k6 load testing for API performance, stress testing, and threshold-based CI checks
Postman
Postman collections, environments, pre-request scripts, tests, and Newman CLI automation
Supertest
Supertest for Node.js HTTP assertion testing with Express, Koa, and Fastify