Mocking APIs with jest.mock() or nock is fragile and tightly coupled to implementation. MSW (Mock Service Worker) intercepts actual network requests — your app doesn't know it's being mocked.
What Is MSW?
MSW intercepts HTTP requests at the network level using Service Workers (browser) or custom interceptors (Node.js). Your application code makes real fetch/axios calls — MSW catches them and returns your mock responses.
Quick Start
npm install msw --save-dev
// mocks/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'John', email: 'john@example.com' },
{ id: 2, name: 'Jane', email: 'jane@example.com' },
]);
}),
http.post('/api/users', async ({ request }) => {
const body = await request.json();
return HttpResponse.json(
{ id: 3, ...body },
{ status: 201 }
);
}),
http.delete('/api/users/:id', ({ params }) => {
return new HttpResponse(null, { status: 204 });
}),
];
Browser Setup
// 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();
}
Test Setup (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());
Testing Example
import { server } from '../mocks/server';
import { http, HttpResponse } from 'msw';
test('shows error when API fails', async () => {
// Override handler for this test only
server.use(
http.get('/api/users', () => {
return HttpResponse.json(
{ message: 'Server error' },
{ status: 500 }
);
})
);
render(<UserList />);
expect(await screen.findByText('Failed to load users')).toBeInTheDocument();
});
Why MSW Over Alternatives
| Feature | MSW | jest.mock | nock |
|---|---|---|---|
| Network level | Yes | No | Node only |
| Framework agnostic | Yes | Jest only | Node only |
| Browser support | Yes | No | No |
| TypeScript | First-class | Manual | Basic |
| Reusable mocks | Yes | Per-test | Per-test |
| Dev server mocking | Yes | No | No |
Advanced Patterns
Simulating Delays
http.get('/api/data', async () => {
await delay(2000); // 2 second delay
return HttpResponse.json({ data: 'loaded' });
});
Network Errors
http.get('/api/data', () => {
return HttpResponse.error(); // Simulates network failure
});
Streaming Responses
http.get('/api/stream', () => {
const stream = new ReadableStream({
start(controller) {
controller.enqueue(new TextEncoder().encode('chunk 1'));
controller.enqueue(new TextEncoder().encode('chunk 2'));
controller.close();
},
});
return new HttpResponse(stream);
});
Get Started
- Documentation
- GitHub — 16K+ stars
- Examples
Testing APIs that consume scraped data? My Apify actors deliver reliable test data. Custom solutions: spinov001@gmail.com
Top comments (0)