msw の使い方

Seamless REST/GraphQL API mocking library for browser and Node.js.

v2.12.14/週MITテスト
AI生成コンテンツ

この記事はAIによって生成されました。内容の正確性は保証されません。最新の情報は公式ドキュメントをご確認ください。

msw(Mock Service Worker)の使い方 — REST/GraphQL APIモッキングの決定版

一言でいうと

msw(Mock Service Worker) は、ブラウザとNode.jsの両方で動作するAPIモッキングライブラリです。Service Worker APIを活用してネットワークレベルでリクエストをインターセプトするため、アプリケーションコードを一切変更せずにAPIのモックを実現できます。

どんな時に使う?

  1. フロントエンド開発時のAPIモック — バックエンドが未完成でも、モックAPIを定義してフロントエンド開発を進められます。ブラウザのDevToolsのNetworkタブにモックレスポンスが表示されるため、実際のAPIと同じ感覚で開発できます。

  2. テスト(ユニット・インテグレーション・E2E) — Jest、Vitest、Playwright、Cypressなどのテストフレームワークと組み合わせて、外部APIへの依存を排除した安定したテストを書けます。fetchaxiosをスタブする必要がありません。

  3. 既存APIの拡張・デバッグ — 本番APIの一部のエンドポイントだけをモックに差し替えて、エッジケースの再現やエラーハンドリングの検証ができます。

インストール

# npm
npm install msw --save-dev

# yarn
yarn add msw --dev

# pnpm
pnpm add msw -D

ブラウザで使用する場合は、Service Workerスクリプトの初期化も必要です:

npx msw init ./public --save

./public はプロジェクトの公開ディレクトリに合わせて変更してください(Next.jsなら ./public、Viteなら ./public など)。

基本的な使い方

mswの基本は「リクエストハンドラを定義し、セットアップ関数に渡す」というシンプルな流れです。

ハンドラの定義(共通)

// src/mocks/handlers.ts
import { http, HttpResponse } from 'msw'

// ユーザー型定義
interface User {
  id: number
  name: string
  email: string
}

export const handlers = [
  // GET リクエストのモック
  http.get('https://api.example.com/users', () => {
    const users: User[] = [
      { id: 1, name: '田中太郎', email: 'tanaka@example.com' },
      { id: 2, name: '佐藤花子', email: 'sato@example.com' },
    ]
    return HttpResponse.json(users)
  }),

  // POST リクエストのモック
  http.post('https://api.example.com/users', async ({ request }) => {
    const newUser = await request.json() as Omit<User, 'id'>
    return HttpResponse.json(
      { id: 3, ...newUser },
      { status: 201 }
    )
  }),
]

ブラウザ環境でのセットアップ

// src/mocks/browser.ts
import { setupWorker } from 'msw/browser'
import { handlers } from './handlers'

export const worker = setupWorker(...handlers)
// src/main.ts(アプリのエントリーポイント)
async function enableMocking() {
  if (process.env.NODE_ENV !== 'development') {
    return
  }
  const { worker } = await import('./mocks/browser')
  return worker.start()
}

enableMocking().then(() => {
  // アプリの起動処理
  // ReactDOM.createRoot(...).render(...)
})

Node.js環境でのセットアップ(テスト用)

// src/mocks/node.ts
import { setupServer } from 'msw/node'
import { handlers } from './handlers'

export const server = setupServer(...handlers)
// vitest.setup.ts(または jest.setup.ts)
import { beforeAll, afterEach, afterAll } from 'vitest'
import { server } from './src/mocks/node'

beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())

よく使うAPI

1. http — RESTリクエストハンドラの定義

Express風のルーティング構文で、HTTPメソッドごとにハンドラを定義します。

import { http, HttpResponse } from 'msw'

// パスパラメータの利用
http.get('https://api.example.com/users/:id', ({ params }) => {
  const { id } = params
  return HttpResponse.json({ id, name: `User ${id}` })
})

// ワイルドカード
http.get('https://api.example.com/files/*', () => {
  return HttpResponse.json({ found: true })
})

// すべてのHTTPメソッドに対応
http.all('https://api.example.com/health', () => {
  return HttpResponse.json({ status: 'ok' })
})

2. HttpResponse — レスポンスの生成

さまざまな形式のレスポンスを生成できます。

import { http, HttpResponse } from 'msw'

// JSONレスポンス
http.get('/api/data', () => {
  return HttpResponse.json(
    { message: 'success' },
    {
      status: 200,
      headers: {
        'X-Custom-Header': 'value',
      },
    }
  )
})

// テキストレスポンス
http.get('/api/text', () => {
  return HttpResponse.text('Hello, World!')
})

// エラーレスポンス
http.get('/api/error', () => {
  return HttpResponse.json(
    { error: 'Not Found' },
    { status: 404 }
  )
})

// ネットワークエラーのシミュレーション
http.get('/api/network-error', () => {
  return HttpResponse.error()
})

// ArrayBufferレスポンス
http.get('/api/binary', () => {
  const buffer = new ArrayBuffer(8)
  return HttpResponse.arrayBuffer(buffer, {
    headers: { 'Content-Type': 'application/octet-stream' },
  })
})

3. graphql — GraphQLリクエストハンドラの定義

import { graphql, HttpResponse } from 'msw'

// 特定エンドポイントのGraphQL
const github = graphql.link('https://api.github.com/graphql')

// Query
const handlers = [
  graphql.query('GetUser', ({ variables }) => {
    const { id } = variables
    return HttpResponse.json({
      data: {
        user: {
          id,
          name: '田中太郎',
        },
      },
    })
  }),

  // Mutation
  graphql.mutation('CreateUser', ({ variables }) => {
    const { input } = variables
    return HttpResponse.json({
      data: {
        createUser: {
          id: 'new-id',
          ...input,
        },
      },
    })
  }),

  // 特定エンドポイント向け
  github.query('GetRepository', () => {
    return HttpResponse.json({
      data: {
        repository: { name: 'msw', stargazerCount: 15000 },
      },
    })
  }),
]

4. server.use() / worker.use() — ランタイムでのハンドラ追加・上書き

テストケースごとに特定のレスポンスを返したい場合に使います。

import { http, HttpResponse } from 'msw'
import { server } from './mocks/node'

test('サーバーエラー時にエラーメッセージが表示される', async () => {
  // このテストだけ500エラーを返す
  server.use(
    http.get('https://api.example.com/users', () => {
      return HttpResponse.json(
        { error: 'Internal Server Error' },
        { status: 500 }
      )
    })
  )

  // テスト実行...
  // afterEach の server.resetHandlers() で元に戻る
})

5. delay / passthrough — レスポンスの遅延とパススルー

import { http, HttpResponse, delay, passthrough } from 'msw'

// レスポンスの遅延(ローディング状態のテストに便利)
http.get('https://api.example.com/slow', async () => {
  await delay(2000) // 2秒遅延
  return HttpResponse.json({ data: 'slow response' })
})

// 無限遅延(レスポンスが返らない状態のテスト)
http.get('https://api.example.com/pending', async () => {
  await delay('infinite')
  return HttpResponse.json({})
})

// パススルー(実際のAPIにリクエストを通す)
http.get('https://api.example.com/real', () => {
  return passthrough()
})

類似パッケージとの比較

特徴mswnockjson-servermiragejs
ブラウザ対応❌(サーバー起動)
Node.js対応
GraphQL対応△(手動)
WebSocket対応✅(v2+)
インターセプト方式Service Worker / ネットワーク層httpモジュールパッチ実サーバー起動XMLHttpRequest/fetchパッチ
DevToolsで確認
TypeScript✅(ファーストクラス)
テスト/開発の共用

選定の指針:

  • ブラウザ+Node.jsの両方で同じモック定義を使いたい → msw
  • Node.jsのテストだけで十分 → nock も選択肢
  • 簡易的なREST APIサーバーが欲しい → json-server

注意点・Tips

1. v1からv2への移行に注意

msw v2(2023年リリース)でAPIが大幅に変更されました。rest.get()http.get()ctx.json()HttpResponse.json() など、書き方が根本的に異なります。ネット上のv1向け記事をそのまま使うと動きません。

// ❌ v1の書き方(動かない)
rest.get('/api/users', (req, res, ctx) => {
  return res(ctx.json({ users: [] }))
})

// ✅ v2の書き方
http.get('/api/users', () => {
  return HttpResponse.json({ users: [] })
})

2. リクエストボディの型安全な取得

interface CreateUserBody {
  name: string
  email: string
}

http.post('https://api.example.com/users', async ({ request }) => {
  const body = (await request.json()) as CreateUserBody
  return HttpResponse.json({ id: 1, ...body }, { status: 201 })
})

3. Cookieの読み取り

http.get('https://api.example.com/me', ({ cookies }) => {
  const { session_id } = cookies

  if (!session_id) {
    return HttpResponse.json(
      { error: 'Unauthorized' },
      { status: 401 }
    )
  }

  return HttpResponse.json({ name: '認証済みユーザー' })
})

4. server.boundary() でスコープを限定する

Node.jsで並行テストを実行する場合、server.use() がグローバルに影響するため競合が起きることがあります。server.boundary() を使うとスコープを限定できます。

import { setupServer } from 'msw/node'
import { http, HttpResponse } from 'msw'

const server = setupServer()

test('並行テストでも安全', () =>
  server.boundary(async () => {
    server.use(
      http.get('/api/data', () => {
        return HttpResponse.json({ scoped: true })
      })
    )
    // このスコープ内でのみ有効
  })()
)

5. 相対パスの扱い

ブラウザ環境では相対パス(/api/users)が自動的に現在のオリジンに解決されますが、Node.js環境ではオリジンが存在しないため、フルURLを指定するか、ベースURLを明示する必要があります。

// ブラウザ → OK
http.get('/api/users', handler)

// Node.js → フルURLを推奨
http.get('https://api.example.com/api/users', handler)

6. onUnhandledRequest の設定

モック定義されていないリクエストの扱いを制御できます。テスト時は error にしておくと、モック漏れを早期に検知できます。

// ブラウザ
await worker.start({
  onUnhandledRequest: 'warn', // 'bypass' | 'warn' | 'error'
})

// Node.js
server.listen({
  onUnhandledRequest: 'error',
})

まとめ

mswは、Service Workerを活用したネットワークレベルのAPIモッキングにより、fetchaxiosのスタブが不要で、アプリケーションコードに一切手を加えずにモックを実現できるライブラリです。ブラウザとNode.jsで同じハンドラ定義を共有できるため、ロー