Msw mocking
QA Skills Directory QA Skills is a curated directory of testing-specific skills for AI coding agents (Claude Code, Cursor, Copilot, etc.).
npx -y skills add PramodDutta/qaskills --skill msw-mockingAssembled from the repository path, not quoted from the project. Check it against their README if it does not work.
What its author says it does
Copied from the file, not written here
Mock Service Worker v2 patterns - http and graphql request handlers, setupServer for Node test runs, setupWorker for the browser, per-test handler overrides, and strict unhandled-request policies.
The file declares its own license as MIT. That is the author’s claim about this one file, and it is not the same thing as the license GitHub reports for the repository, which is listed with the other numbers below.
SKILL.md
9.6 KB, ~2.2k tokens by cl100k_base, as published. Nobody here has run it
MSW API Mocking
This skill makes an AI agent mock HTTP and GraphQL APIs at the network level with Mock Service Worker v2: one set of request handlers shared between Vitest/Jest (via setupServer) and the browser (via setupWorker), per-test overrides with server.use, and an onUnhandledRequest: 'error' policy that catches drift. Trigger it when components or services call fetch/axios in tests, when msw appears in package.json, or when the user is stubbing global.fetch by hand and suffering for it.
Core Principles
- Mock the network, not the module.
vi.mock('./api-client')couples tests to an import path and skips serialization, query strings, and status handling. MSW intercepts actual requests, so the entire client stack (interceptors, retries, parsing) stays under test. - One
handlers.tsis the contract. Define happy-path handlers once; tests, Storybook, and local dev all consume the same array. When the real API changes, you update one file and every consumer notices. - Happy path in global handlers, failures per test. The default handlers return realistic success responses. Error cases (
500,422, timeouts) are declared inside the test that needs them viaserver.use(...), which prepends a one-off override. onUnhandledRequest: 'error'always. Any request without a handler should fail the test loudly. Silent passthrough is how a "unit" test ends up hitting production from CI.- Reset handlers after every test.
server.resetHandlers()inafterEachremoves per-test overrides; without it, test order starts to matter and the suite rots. - Respond with realistic shapes and status codes. Use the same field names, casing, pagination envelopes, and error bodies the real API returns; mocks that drift teach your code to handle an API that does not exist.
Setup
npm install --save-dev msw
# Browser usage only: place the worker script in your static dir
npx msw init public/ --save
Shared handlers
// src/mocks/handlers.ts
import { http, HttpResponse, delay } from 'msw';
export interface User {
id: string;
name: string;
role: 'admin' | 'member';
}
export const handlers = [
http.get('https://api.example.com/users/:id', ({ params }) => {
return HttpResponse.json<User>({
id: String(params.id),
name: 'Ada Lovelace',
role: 'admin',
});
}),
http.get('https://api.example.com/orders', ({ request }) => {
const url = new URL(request.url);
const page = Number(url.searchParams.get('page') ?? '1');
return HttpResponse.json({
items: [{ id: 'ord_1', total: 4999 }],
page,
totalPages: 3,
});
}),
http.post('https://api.example.com/orders', async ({ request }) => {
const body = (await request.json()) as { sku?: string; qty?: number };
if (!body.sku) {
return HttpResponse.json({ error: 'sku is required' }, { status: 422 });
}
await delay(50); // simulate realistic latency
return HttpResponse.json({ orderId: 'ord_2', ...body }, { status: 201 });
}),
];
Patterns
1. Node test setup (Vitest or Jest)
// src/mocks/server.ts
import { setupServer } from 'msw/node';
import { handlers } from './handlers';
export const server = setupServer(...handlers);
// vitest.setup.ts (register via test.setupFiles in vitest.config.ts)
import { beforeAll, afterEach, afterAll } from 'vitest';
import { server } from './src/mocks/server';
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
2. Testing a component, then overriding for the failure case
// src/components/UserProfile.test.tsx
import { render, screen } from '@testing-library/react';
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/server';
import { UserProfile } from './UserProfile';
it('renders the user fetched from the API', async () => {
render(<UserProfile id="42" />);
expect(await screen.findByRole('heading', { name: 'Ada Lovelace' })).toBeInTheDocument();
});
it('shows an error banner when the API is down', async () => {
// One-off override; resetHandlers() in afterEach removes it
server.use(
http.get('https://api.example.com/users/:id', () =>
HttpResponse.json({ message: 'internal error' }, { status: 500 }),
),
);
render(<UserProfile id="42" />);
expect(await screen.findByRole('alert')).toHaveTextContent('Could not load profile');
});
it('handles a network-level failure distinctly from a 500', async () => {
server.use(
http.get('https://api.example.com/users/:id', () => HttpResponse.error()),
);
render(<UserProfile id="42" />);
expect(await screen.findByRole('alert')).toHaveTextContent('Check your connection');
});
3. Asserting on the request your code sent
// src/api/orders.test.ts
import { http, HttpResponse } from 'msw';
import { server } from '../mocks/server';
import { createOrder } from './orders';
it('sends the auth header and JSON body the API expects', async () => {
let captured: { auth: string | null; body: unknown } | undefined;
server.use(
http.post('https://api.example.com/orders', async ({ request }) => {
captured = {
auth: request.headers.get('authorization'),
body: await request.json(),
};
return HttpResponse.json({ orderId: 'ord_9' }, { status: 201 });
}),
);
await createOrder({ sku: 'SKU-1', qty: 2 }, { token: 'jwt-abc' });
expect(captured?.auth).toBe('Bearer jwt-abc');
expect(captured?.body).toEqual({ sku: 'SKU-1', qty: 2 });
});
4. GraphQL operations
// src/mocks/graphql-handlers.ts
import { graphql, HttpResponse } from 'msw';
export const gqlHandlers = [
graphql.query('GetCart', ({ variables }) => {
return HttpResponse.json({
data: {
cart: { id: variables.cartId, items: [{ sku: 'SKU-1', qty: 1 }] },
},
});
}),
graphql.mutation('AddToCart', () => {
return HttpResponse.json({
errors: [{ message: 'Out of stock', extensions: { code: 'OUT_OF_STOCK' } }],
});
}),
];
5. Browser worker for dev and Storybook
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser';
import { handlers } from './handlers';
export const worker = setupWorker(...handlers);
// src/main.tsx -- enable mocking only in development
async function enableMocking(): Promise<void> {
if (!import.meta.env.DEV) return;
const { worker } = await import('./mocks/browser');
await worker.start({ onUnhandledRequest: 'bypass' });
}
enableMocking().then(() => {
createRoot(document.getElementById('root')!).render(<App />);
});
Best Practices
- Type your response bodies (
HttpResponse.json<User>(...)) so mock drift becomes a compile error when the app's types change. - Use
delay()in handlers that back loading-state tests; a 0ms response can resolve before React renders the spinner you are asserting on. - Keep path params (
:id) andURLquery parsing in handlers instead of one handler per exact URL; fewer handlers, broader coverage. - For paginated endpoints, drive the response from
searchParamsso the same handler serves page 1 and page 7 tests. - In Jest (not Vitest), polyfill as needed per MSW docs and register the setup file via
setupFilesAfterEach/setupFilesAfterEach-equivalent (setupFilesAfterEachis Vitest; Jest usessetupFilesAfterEach? usesetupFilesAfterEachcarefully) - concretely:setupFiles: ['<rootDir>/jest.setup.ts']with the same listen/reset/close trio. - Co-locate one-off overrides with the test that needs them; if three tests need the same failure handler, promote it to a named export in
handlers.ts.
Anti-Patterns
- Stubbing
global.fetch = vi.fn()and hand-craftingResponseobjects: brittle, skips URL matching, and dies the day you switch to axios. onUnhandledRequest: 'bypass'in tests: unmocked calls silently reach real services, making tests slow, flaky, and occasionally destructive.- Defining error-case handlers globally so every test starts from a broken API and "fixes" it with overrides - invert it.
- Forgetting
server.resetHandlers()inafterEach, then debugging why a 500 override leaks into the next twelve tests. - Mocking your own server's routes in end-to-end tests; MSW is for unit/integration layers, E2E should hit a real (containerized) backend.
- Duplicate handler arrays per test file drifting apart; share
handlers.tsand override locally.
When to Trigger This Skill
- Tests stub
fetch,axios, or API client modules by hand, or a component test suite needs network responses. mswis in package.json,mockServiceWorker.jsis inpublic/, orsetupServer/setupWorkerappears in the codebase.- The user asks to "mock an API", "test loading and error states", "share mocks between tests and Storybook", or "stop tests from hitting the real API".
- A GraphQL client (Apollo, urql, graphql-request) needs operation-level mocks by query name.
- Frontend development is blocked on an unfinished backend and needs a realistic mock layer that later doubles as test fixtures.