Vitest Cheatsheet

Network Mocking (MSW)

Use this Vitest reference while you build software engineering projects, review code for technical interview prep, or polish examples for a software engineer resume.

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.