結論:MSW v2 はハンドラーAPIを根本から刷新した
MSW(Mock Service Worker)の v1 → v2 アップグレードはハンドラーの記述方法を根本から変更する破壊的変更が含まれる。npm update msw でバージョンを上げただけでは、既存のハンドラーはすべて壊れる。
最初に変更点の全体像をまとめる:
| v1 | v2 | 変更内容 |
|---|---|---|
import { rest } from 'msw' |
import { http } from 'msw' |
エクスポート名の変更 |
rest.get(url, (req, res, ctx) => {...}) |
http.get(url, ({ request }) => {...}) |
ハンドラー関数の引数変更 |
res(ctx.json({...})) |
HttpResponse.json({...}) |
レスポンス生成方法の変更 |
ctx.delay(300) |
await delay(300) |
遅延処理の変更 |
req.body |
await request.json() |
リクエストボディ取得の変更 |
req.url.searchParams |
new URL(request.url).searchParams |
URLパラメータ取得の変更 |
こんなエラーが出たら MSW v2 の破壊的変更が原因
v1 のコードを v2 で動かすと、以下のようなエラーが出る:
TypeError: rest is not defined
SyntaxError: The requested module 'msw' does not provide an export named 'rest'
TypeError: Cannot destructure property 'req' of '...' as it is undefined
TypeError: res is not a function
あるいは TypeScript を使っている場合:
error TS2305: Module '"msw"' has no exported member 'rest'.
error TS2345: Argument of type '(req: MockedRequest, res: ResponseComposition, ctx: RestContext) => ResponseTransformer' is not assignable to parameter
これらはすべて v2 で rest が廃止されたことに起因する。
対策1:ハンドラーのインポートと記述を移行する(最重要)
v1 の書き方
import { rest } from 'msw'
export const handlers = [
rest.get('/api/user', (req, res, ctx) => {
return res(
ctx.status(200),
ctx.json({ id: 1, name: '田中太郎' })
)
}),
rest.post('/api/login', (req, res, ctx) => {
return res(ctx.json({ token: 'abc123' }))
}),
]
v2 の書き方
import { http, HttpResponse } from 'msw'
export const handlers = [
http.get('/api/user', () => {
return HttpResponse.json({ id: 1, name: '田中太郎' })
}),
http.post('/api/login', () => {
return HttpResponse.json({ token: 'abc123' })
}),
]
変更点まとめ:
rest→httpに名前変更(インポートも変わる)- ハンドラー関数の引数
(req, res, ctx)→()または({ request, params, cookies }) res(ctx.json({...}))→return HttpResponse.json({...})(resとctxが廃止)
対策2:ステータスコード・ヘッダーの指定方法
v1 の書き方
rest.get('/api/data', (req, res, ctx) => {
return res(
ctx.status(404),
ctx.set('X-Custom-Header', 'value'),
ctx.json({ error: 'Not Found' })
)
})
v2 の書き方
http.get('/api/data', () => {
return HttpResponse.json(
{ error: 'Not Found' },
{
status: 404,
headers: { 'X-Custom-Header': 'value' },
}
)
})
HttpResponse.json(body, init) の第2引数に、status・headers などを渡す。これは標準の Response コンストラクタの ResponseInit と同じ形式だ。
対策3:リクエストボディの取得方法
POST/PUT リクエストのボディを読み取る部分も大きく変わった。
v1 の書き方
rest.post('/api/create', (req, res, ctx) => {
const { name, email } = req.body as { name: string; email: string }
return res(ctx.json({ id: 1, name, email }))
})
v2 の書き方
http.post('/api/create', async ({ request }) => {
const { name, email } = await request.json() as { name: string; email: string }
return HttpResponse.json({ id: 1, name, email })
})
ポイント:
requestは標準の Fetch APIRequestオブジェクト- ボディ取得は
await request.json()・await request.text()・await request.formData()などを使う - ハンドラー関数が
asyncになる
URLパラメータ・クエリパラメータも変わった
// v1
rest.get('/api/user/:id', (req, res, ctx) => {
const { id } = req.params
const page = req.url.searchParams.get('page')
return res(ctx.json({ id, page }))
})
// v2
http.get('/api/user/:id', ({ request, params }) => {
const { id } = params
const url = new URL(request.url)
const page = url.searchParams.get('page')
return HttpResponse.json({ id, page })
})
対策4:遅延レスポンスの書き方
ネットワーク遅延をシミュレートする ctx.delay() も変わった。
v1 の書き方
rest.get('/api/slow', (req, res, ctx) => {
return res(
ctx.delay(1000),
ctx.json({ data: 'loaded' })
)
})
v2 の書き方
import { http, HttpResponse, delay } from 'msw'
http.get('/api/slow', async () => {
await delay(1000)
return HttpResponse.json({ data: 'loaded' })
})
delay は msw から独立してインポートし、await して使う。
対策5:setupServer(Node.js / テスト環境)の変更
Vitest・Jest などのテスト環境で使う setupServer の設定も確認が必要だ。
v1 の書き方
// src/mocks/server.ts
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
// vitest.setup.ts
import { server } from './src/mocks/server'
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
v2 の書き方
インターフェースは v1 とほぼ同じ。ただし handlers が v2 形式(http.*)に書き換えられていることが前提:
// src/mocks/server.ts
import { setupServer } from 'msw/node'
import { handlers } from './handlers' // ← v2形式のhandlersに変更済みであること
export const server = setupServer(...handlers)
// vitest.setup.ts
import { server } from './src/mocks/server'
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }))
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
v2 では onUnhandledRequest: 'error' を推奨設定として使うと、ハンドラーが定義されていないリクエストで即座にテストを落としてくれるので、モック漏れに気づきやすい。
対策6:setupWorker(ブラウザ環境)の変更
ブラウザで使う setupWorker も確認しておく。
// src/mocks/browser.ts
// v1
import { setupWorker, rest } from 'msw'
import { handlers } from './handlers'
export const worker = setupWorker(...handlers)
// v2
import { setupWorker } from 'msw/browser' // ← インポートパスが変わった
import { handlers } from './handlers'
export const worker = setupWorker(...handlers)
v2 では msw/browser という明示的なサブパスからインポートする。msw からの直接インポートだとブラウザ環境で動作しない場合がある。
対策7:エラーレスポンスのショートカット
v2 では HttpResponse に便利なスタティックメソッドが追加された:
import { http, HttpResponse } from 'msw'
http.get('/api/data', () => {
// JSON レスポンス
return HttpResponse.json({ key: 'value' })
// テキストレスポンス
return HttpResponse.text('plain text')
// エラーレスポンス
return HttpResponse.json({ error: 'Unauthorized' }, { status: 401 })
// ネットワークエラーのシミュレート
return HttpResponse.error()
})
v1 → v2 移行チェックリスト
大規模なコードベースで移行する際のチェックリスト:
- [ ]
import { rest } from 'msw'→import { http, HttpResponse } from 'msw'に変更 - [ ]
rest.get/post/put/delete/patch→http.get/post/put/delete/patchに変更 - [ ] ハンドラー引数
(req, res, ctx)→({ request, params, cookies })に変更 - [ ]
res(ctx.json({...}))→return HttpResponse.json({...})に変更 - [ ]
ctx.status(N)→HttpResponse.json(body, { status: N })に変更 - [ ]
ctx.set('Header', 'value')→HttpResponse.json(body, { headers: {...} })に変更 - [ ]
req.body→await request.json()に変更(ハンドラーを async に) - [ ]
req.params→params(引数から分割代入)に変更 - [ ]
req.url.searchParams→new URL(request.url).searchParamsに変更 - [ ]
ctx.delay(N)→import { delay } from 'msw'してawait delay(N)に変更 - [ ] ブラウザ用は
import { setupWorker } from 'msw/browser'に変更
package.json でバージョンを確認する
現在のバージョンが v1 か v2 かを確認するには:
npm list msw
# または
cat package.json | grep msw
v1 系は ^1.x.x、v2 系は ^2.x.x。意図せず v2 に上がっていないか確認しよう。
{
"devDependencies": {
"msw": "^2.0.0"
}
}
まとめ
MSW v2 の破壊的変更は一見大きく見えるが、v2 のハンドラーは標準の Fetch API(Request / Response)ベースになったという思想を理解すると見通しがよくなる。
| 旧API(v1) | 新API(v2) | 理由 |
|---|---|---|
ctx.json() |
HttpResponse.json() |
標準 Response に近づけた |
req.body |
await request.json() |
標準 Request API に準拠 |
ctx.delay() |
await delay() |
async/await 自然な形に |
移行ガイドは 公式マイグレーションドキュメント も参照してほしい。一括置換できる箇所も多いので、プロジェクト規模によっては1〜2時間で完了する。
