← 記事一覧に戻る

MSW v2 で動かない・テストが落ちる時の移行手順と対策【rest→http / ctx.json→HttpResponse】

結論: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' })
  }),
]

変更点まとめ:

  • resthttp に名前変更(インポートも変わる)
  • ハンドラー関数の引数 (req, res, ctx)() または ({ request, params, cookies })
  • res(ctx.json({...}))return HttpResponse.json({...})resctx が廃止)

対策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引数に、statusheaders などを渡す。これは標準の 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 API Request オブジェクト
  • ボディ取得は 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' })
})

delaymsw から独立してインポートし、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/patchhttp.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.bodyawait request.json() に変更(ハンドラーを async に)
  • [ ] req.paramsparams(引数から分割代入)に変更
  • [ ] req.url.searchParamsnew 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時間で完了する。