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.
npx skills add https://github.com/PramodDutta/qaskills --skill 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.
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.handlers.ts is 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.500, 422, timeouts) are declared inside the test that needs them via server.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.server.resetHandlers() in afterEach removes per-test overrides; without it, test order starts to matter and the suite rots.npm install --save-dev msw
# Browser usage only: place the worker script in your static dir
npx msw init public/ --save
// 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 });
}),
];
// 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());
// 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');
});
// 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 });
});
// 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' } }],
});
}),
];
// 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 />);
});
HttpResponse.json<User>(...)) so mock drift becomes a compile error when the app's types change.delay() in handlers that back loading-state tests; a 0ms response can resolve before React renders the spinner you are asserting on.:id) and URL query parsing in handlers instead of one handler per exact URL; fewer handlers, broader coverage.searchParams so the same handler serves page 1 and page 7 tests.setupFilesAfterEach/setupFilesAfterEach-equivalent (setupFilesAfterEach is Vitest; Jest uses setupFilesAfterEach? use setupFilesAfterEach carefully) - concretely: setupFiles: ['<rootDir>/jest.setup.ts'] with the same listen/reset/close trio.handlers.ts.global.fetch = vi.fn() and hand-crafting Response objects: 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.server.resetHandlers() in afterEach, then debugging why a 500 override leaks into the next twelve tests.handlers.ts and override locally.fetch, axios, or API client modules by hand, or a component test suite needs network responses.msw is in package.json, mockServiceWorker.js is in public/, or setupServer/setupWorker appears in the codebase.Use when implementation is complete, all tests pass, and you need to decide how to integrate the work - guides completion of development work by presenting structured options for merge, PR, or cleanup
Comprehensive GitHub release orchestration with AI swarm coordination for automated versioning, testing, deployment, and rollback management
Migrate test files from `as` type assertions to @total-typescript/shoehorn. Use when user mentions shoehorn, wants to replace `as` in tests, or needs partial test data.
Modern JavaScript/TypeScript development with Bun runtime. Covers package management, bundling, testing, and migration from Node.js. Use when working with Bun, optimizing JS/TS development speed, or migrating from Node.js to Bun.
You are a dependency management expert specializing in safe, incremental upgrades of project dependencies. Plan and execute dependency updates with minimal risk, proper testing, and clear migration pa
Master systematic debugging techniques, profiling tools, and root cause analysis to efficiently track down bugs across any codebase or technology stack. Use when investigating bugs, performance issues, or unexpected behavior.
Opinionated backend development standards for Node.js + Express + TypeScript microservices. Covers layered architecture, BaseController pattern, dependency injection, Prisma repositories, Zod validation, unifiedConfig, Sentry error tracking, async safety, and testing discipline.
Best practices for writing JavaScript/TypeScript tests using Jest, including mocking strategies, test structure, and common patterns.
Take pramoddutta/msw api mocking from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npm, npx.
Without those the skill loads but fails at the first command.