API Mocking With MSW: Share Mock Handlers Between Unit Tests and Playwright E2E
Mock Service Worker (MSW) intercepts requests at the network level — no application code changes needed. Perfect for frontend testing when backend is unavailable, unstable, or slow.
🎭 Want to master this with real projects? Join the Playwright Automation Mastery course at The Testing Academy.
Contents
MSW vs Playwright page.route()
| Feature | MSW | page.route() |
|---|---|---|
| Works without browser | Yes (Node.js) | No (needs page) |
| Shared between unit + E2E | Yes | No (E2E only) |
| Request matching | REST + GraphQL handlers | URL pattern matching |
| TypeScript handlers | Strongly typed | Generic |
| Setup complexity | Service worker config | One line per route |
Setup MSW
npm install -D msw
Define Handlers
// mocks/handlers.ts
import { http, HttpResponse } from 'msw';
export const handlers = [
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'John', email: 'john@test.com' },
{ id: 2, name: 'Jane', email: 'jane@test.com' },
]);
}),
http.post('/api/users', async ({ request }) => {
const body = await request.json();
return HttpResponse.json(
{ id: 3, ...body },
{ status: 201 }
);
}),
http.get('/api/users/:id', ({ params }) => {
if (params.id === '999') {
return HttpResponse.json(
{ message: 'User not found' },
{ status: 404 }
);
}
return HttpResponse.json({ id: params.id, name: 'User ' + params.id });
}),
];
🚀 Level Up Your Playwright
From locators to CI pipelines — build a production-grade Playwright + TypeScript framework step by step.
Use in Playwright Tests
// Inject MSW handlers via page.route() bridge
import { handlers } from '../mocks/handlers';
test.beforeEach(async ({ page }) => {
for (const handler of handlers) {
// Convert MSW handlers to page.route() calls
await page.route(handler.info.path, async route => {
const response = await handler.resolver(/* ... */);
await route.fulfill({
status: response.status,
body: JSON.stringify(response.body),
});
});
}
});
Error Scenario Testing
// Override handler for specific test
test('handle server error', async ({ page }) => {
await page.route('**/api/users', route => {
route.fulfill({ status: 500, body: '{"error":"Internal Server Error"}' });
});
await page.goto('/users');
await expect(page.getByText('Failed to load users')).toBeVisible();
});
GraphQL Mocking with MSW
import { graphql, HttpResponse } from 'msw';
export const graphqlHandlers = [
graphql.query('GetUsers', () => {
return HttpResponse.json({
data: {
users: [{ id: 1, name: 'John' }]
}
});
}),
graphql.mutation('CreateUser', async ({ variables }) => {
return HttpResponse.json({
data: {
createUser: { id: 99, name: variables.name }
}
});
}),
];
🎓 Master Playwright End to End
Join hundreds of SDETs building real automation frameworks. Lifetime access, hands-on projects, and a job-ready portfolio.
