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

StrategyEffect
'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 (not res(ctx.json(...))). This cheatsheet covers v2.
  • onUnhandledRequest: 'error' — catches missing handlers early; remove for gradual adoption.