MSW Handlers Guide
REST Handlers
// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw'
export const handlers = [
// GET — return JSON
http.get('/api/users', () => {
return HttpResponse.json([
{ id: 1, name: 'Alice', email: 'alice@example.com' },
{ id: 2, name: 'Bob', email: 'bob@example.com' },
])
}),
// GET with URL params
http.get('/api/users/:id', ({ params }) => {
const { id } = params
if (id === '999') {
return new HttpResponse(null, { status: 404 })
}
return HttpResponse.json({ id: Number(id), name: 'Alice' })
}),
// POST — read request body
http.post('/api/users', async ({ request }) => {
const body = await request.json()
return HttpResponse.json(
{ id: 3, ...body },
{ status: 201 }
)
}),
// DELETE
http.delete('/api/users/:id', () => {
return new HttpResponse(null, { status: 204 })
}),
]
GraphQL Handlers
import { graphql, HttpResponse } from 'msw'
export const handlers = [
graphql.query('GetUser', ({ variables }) => {
const { id } = variables
return HttpResponse.json({
data: {
user: { id, name: 'Alice', email: 'alice@example.com' },
},
})
}),
graphql.mutation('CreateUser', async ({ variables }) => {
const { name, email } = variables
return HttpResponse.json({
data: {
createUser: { id: 99, name, email },
},
})
}),
// Error response
graphql.query('GetProtected', () => {
return HttpResponse.json({
errors: [{ message: 'Unauthorized', extensions: { code: 'UNAUTHORIZED' } }],
})
}),
]
Browser Setup
// Initialize service worker
// npx msw init public/ --save
// src/mocks/browser.ts
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'
export const worker = setupWorker(...handlers)
// src/main.tsx
async function enableMocking() {
if (process.env.NODE_ENV !== 'development') return
const { worker } = await import('./mocks/browser')
return worker.start({
onUnhandledRequest: 'warn', // warn on unhandled requests
})
}
enableMocking().then(() => {
ReactDOM.createRoot(document.getElementById('root')!).render(<App />)
})
Node.js Setup — Jest Integration
// src/mocks/server.ts
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
// jest.setup.ts
import { server } from './src/mocks/server'
import '@testing-library/jest-dom'
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
// In a test — override handler for one test
import { http, HttpResponse } from 'msw'
import { server } from '../mocks/server'
import { render, screen, waitFor } from '@testing-library/react'
test('shows error when API fails', async () => {
server.use(
http.get('/api/users', () => {
return HttpResponse.json({ error: 'Server Error' }, { status: 500 })
})
)
render(<UserList />)
await waitFor(() => screen.getByText('Failed to load users'))
})
Advanced Response Resolvers
import { http, HttpResponse, delay } from 'msw'
export const handlers = [
// With artificial delay
http.get('/api/slow', async () => {
await delay(1500)
return HttpResponse.json({ data: 'slow response' })
}),
// Custom headers
http.get('/api/data', () => {
return new HttpResponse(JSON.stringify({ items: [] }), {
status: 200,
headers: {
'Content-Type': 'application/json',
'X-Total-Count': '42',
'Cache-Control': 'no-cache',
},
})
}),
// Network error simulation
http.get('/api/broken', () => {
return HttpResponse.error()
}),
// Passthrough — don't mock this path
http.get('/api/real-endpoint', ({ request }) => {
return fetch(request) // pass through to real server
}),
// Read search params
http.get('/api/search', ({ request }) => {
const url = new URL(request.url)
const q = url.searchParams.get('q')
return HttpResponse.json([{ id: 1, title: `Result for ${q}` }])
}),
]
Playwright Integration
// playwright/fixtures.ts — MSW in Playwright via route mocking
// Option 1: Use Playwright's built-in route mocking (recommended for E2E)
test('mocks API with Playwright routes', async ({ page }) => {
await page.route('/api/users', async route => {
await route.fulfill({
status: 200,
contentType: 'application/json',
body: JSON.stringify([{ id: 1, name: 'Alice' }]),
})
})
await page.goto('/users')
await expect(page.getByText('Alice')).toBeVisible()
})
// Option 2: Use msw with Playwright (experimental)
// playwright/fixtures.ts
import { createServer } from 'http'
import { createMiddleware } from '@mswjs/http-middleware'
import { handlers } from './mocks/handlers'
export async function startMockServer() {
const middleware = createMiddleware(...handlers)
const server = createServer(middleware)
await new Promise(resolve => server.listen(9090, resolve))
return server
}