msw(Mock Service Worker)の使い方 — REST/GraphQL APIモッキングの決定版
一言でいうと
msw(Mock Service Worker) は、ブラウザとNode.jsの両方で動作するAPIモッキングライブラリです。Service Worker APIを活用してネットワークレベルでリクエストをインターセプトするため、アプリケーションコードを一切変更せずにAPIのモックを実現できます。
どんな時に使う?
-
フロントエンド開発時のAPIモック — バックエンドが未完成でも、モックAPIを定義してフロントエンド開発を進められます。ブラウザのDevToolsのNetworkタブにモックレスポンスが表示されるため、実際のAPIと同じ感覚で開発できます。
-
テスト(ユニット・インテグレーション・E2E) — Jest、Vitest、Playwright、Cypressなどのテストフレームワークと組み合わせて、外部APIへの依存を排除した安定したテストを書けます。
fetchやaxiosをスタブする必要がありません。 -
既存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()
})
類似パッケージとの比較
| 特徴 | msw | nock | json-server | miragejs |
|---|---|---|---|---|
| ブラウザ対応 | ✅ | ❌ | ❌(サーバー起動) | ✅ |
| 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モッキングにより、fetchやaxiosのスタブが不要で、アプリケーションコードに一切手を加えずにモックを実現できるライブラリです。ブラウザとNode.jsで同じハンドラ定義を共有できるため、ロー