Vitest Cheatsheet
Network Mocking (MSW)
Use this Vitest reference while you build software engineering projects, review code, or refresh the syntax you reach for most.
What is MSW?
Mock Service Worker intercepts HTTP requests at the network layer — your code runs as normal, but responses are intercepted by your handlers. Works in both browser (via Service Worker) and Node (via interceptors).
Installation
npm install -D msw
Defining Request Handlers
// src/mocks/handlers.ts import { http, HttpResponse } from 'msw' export const handlers = [ // GET with path params http.get('/api/users/:id', ({ params }) => { const { id } = params return HttpResponse.json({ id, name: 'Alice' }) }), // POST with request body http.post('/api/users', async ({ request }) => { const body = await request.json() return HttpResponse.json({ id: '123', ...body }, { status: 201 }) }), // Simulate error http.get('/api/broken', () => { return HttpResponse.json({ message: 'Internal Server Error' }, { status: 500 }) }), // Network error (no response) http.get('/api/timeout', () => { return HttpResponse.error() }), ]
Setting Up the Node Server (for Vitest)
// src/mocks/server.ts import { setupServer } from 'msw/node' import { handlers } from './handlers' export const server = setupServer(...handlers)
Global Setup in setupFiles
// src/test/setup.ts import { beforeAll, afterEach, afterAll } from 'vitest' import { server } from '../mocks/server' beforeAll(() => server.listen({ onUnhandledRequest: 'error' })) afterEach(() => server.resetHandlers()) afterAll(() => server.close())
// vitest.config.ts export default defineConfig({ test: { setupFiles: ['./src/test/setup.ts'], }, })
Using MSW in Tests
import { describe, it, expect } from 'vitest' import { fetchUser } from './api' it('fetches a user', async () => { const user = await fetchUser(1) expect(user.name).toBe('Alice') // MSW handler returns this })
Overriding Handlers Per Test
import { server } from '../mocks/server' import { http, HttpResponse } from 'msw' it('handles 404', async () => { server.use( http.get('/api/users/:id', () => HttpResponse.json({ message: 'Not found' }, { status: 404 }) ) ) await expect(fetchUser(999)).rejects.toThrow('Not found') }) // handler is reset after each test because of server.resetHandlers() in setup
One-Time Handler Override
server.use( http.get('/api/data', () => HttpResponse.json({ val: 42 }), { once: true }) ) await fetchData() // returns { val: 42 } await fetchData() // falls through to original handler
HTTP Methods
import { http, HttpResponse } from 'msw' http.get('/path', resolver) http.post('/path', resolver) http.put('/path', resolver) http.patch('/path', resolver) http.delete('/path', resolver) http.head('/path', resolver) http.options('/path', resolver)
Reading Request Data
http.post('/api/login', async ({ request, params, cookies }) => { const body = await request.json() // JSON body const text = await request.text() // text body const form = await request.formData() // FormData const url = new URL(request.url) const q = url.searchParams.get('query') // query params const auth = request.headers.get('Authorization') return HttpResponse.json({ token: 'abc' }) })
Returning Different Response Types
// JSON HttpResponse.json({ key: 'value' }, { status: 200 }) // Text HttpResponse.text('plain text', { status: 200 }) // XML new HttpResponse('<root/>', { headers: { 'Content-Type': 'application/xml' } }) // Blob/ArrayBuffer new HttpResponse(new Blob(['binary']), { headers: { 'Content-Type': 'application/octet-stream' } }) // Empty (204) new HttpResponse(null, { status: 204 }) // Network failure (no response) HttpResponse.error()
Passthrough (Let Real Request Through)
import { passthrough } from 'msw' http.get('/api/real-endpoint', () => passthrough())
GraphQL Handlers
import { graphql, HttpResponse } from 'msw' export const handlers = [ graphql.query('GetUser', ({ variables }) => { return HttpResponse.json({ data: { user: { id: variables.id, name: 'Alice' } }, }) }), graphql.mutation('CreateUser', ({ variables }) => { return HttpResponse.json({ data: { createUser: { id: '1', name: variables.name } }, }) }), ]
Browser Setup (for Storybook / Component Tests)
// src/mocks/browser.ts import { setupWorker } from 'msw/browser' import { handlers } from './handlers' export const worker = setupWorker(...handlers)
// main.tsx (dev only) if (import.meta.env.DEV) { const { worker } = await import('./mocks/browser') await worker.start() }
Generate the Service Worker:
npx msw init public/ --save
Unhandled Request Strategies
| Strategy | Effect |
|---|---|
'bypass' | Let the request through (default) |
'warn' | Console warning for unhandled |
'error' | Throw an error (recommended for tests) |
server.listen({ onUnhandledRequest: 'error' })
Common Gotchas
- Forget
afterEach(() => server.resetHandlers())— per-test overrides bleed into other tests. HttpResponse.error()vs 500 —.error()simulates a network failure (no response); a 500 status is a valid HTTP response.- MSW v1 vs v2 API — v2 uses
HttpResponse(notres(ctx.json(...))). This cheatsheet covers v2. onUnhandledRequest: 'error'— catches missing handlers early; remove for gradual adoption.