mcpField
W
web-mocks-msw

web-mocks-msw

github.com/agents-incテスト・品質2671 回インストール2 日前に更新
リポジトリ

web-mocks-msw について

web-mocks-mswは、開発やテストでMSWを使ってネットワークリクエストをモックするガイドスキルです。ブラウザーとNodeでハンドラーを共有し、応答バリエーションやテスト単位の上書きを管理する方法を示します。

主な機能

  1. 01ブラウザーとNodeでハンドラーを共有
  2. 02再利用可能な型付きレスポンスデータ
  3. 03標準・空・エラーの応答バリエーション
  4. 04テスト単位のハンドラー上書き
  5. 05遅延と実行時バリエーションの設定

活用シーン

  1. 01バックエンド完成前のAPIモック
  2. 02空・エラー・遅延応答のテスト
  3. 03テストごとに特定エンドポイントを上書き
  4. 04開発中の応答バリエーション切り替え

インストール

コマンドをコピーしてターミナルで実行します。

$npx skills add https://github.com/agents-inc/skills --skill web-mocks-msw

インストール後、Claude Code で/web-mocks-mswを入力して読み込まれたか確認します。

同梱ファイル

SKILL.md と一緒にインストールされる参考資料やスクリプト。

metadata.yaml
examples/
browser.md
core.md

詳細

MSW パターン

クイックガイド: MSW はネットワーク層でリクエストをインターセプトするため、アプリケーションコードはモックされていることを認識しません。1 つのハンドラーセットを両方の環境で使用できます。開発では msw/browser の setupWorker、テストでは msw/node の setupServer を使います。この 2 つは相互に置き換えられません。レスポンスボディは独立したモジュールに置き、各バリアント(デフォルト、空、エラー)を再利用可能にし、API の生成された型に対して型付けします。

詳細なリソース:

  • examples/core.md — モックデータモジュール、バリアントハンドラー、サーバーのセットアップとテストのライフサイクル、テストごとのオーバーライド、実行時のバリアント切り替え、遅延のシミュレーション
  • examples/browser.md — クライアントレンダリングおよびサーバーレンダリングのエントリーポイント向けのブラウザーワーカーのセットアップとアプリの統合

該当する方法

ハンドラーは共通で、異なるのはセットアップモジュールとライフサイクルだけです。

  • 開発時のブラウザー — msw/browser の setupWorker を使用し、アプリのレンダリング前に完了を待機します。実行時のバリアント切り替えを使って、UI の各状態を確認できます。examples/browser.md に従ってください。
  • テスト時の Node — msw/node の setupServer を使用し、テストスイートの開始時にリクエストの待ち受けを開始して、各テストの間にリセットします。1 つのテストだけのオーバーライドには server.use() を使用します。examples/core.md に従ってください。

<critical_requirements>

MSW コードを書く前に

環境に合ったセットアップ関数を使用してください。 setupWorker はサービスワーカー API を必要とし、setupServer は Node のリクエスト層をパッチするため、逆の環境で使うと入れ替えが原因だとは分からず、不足しているグローバル変数を示すエラーが発生します。

afterEach で server.resetHandlers() を呼び出してハンドラーをリセットしてください。 これにより、server.use() のオーバーライドが次のテストの結果を左右するのを防げます。

レンダリング前に worker.start() の完了を待機してください。 ワーカーの準備が整う前に送信されたリクエストは実際のネットワークに到達するため、テストスイートの最初のレンダリングだけが、ほかのレンダリングと異なる結果になることがあります。

レスポンスボディは独立したモジュールに置き、API の生成された型に対して型付けしてください。 1 つのフィクスチャを複数のハンドラーで使用でき、スキーマの変更はアサーションの実行時ではなくコンパイル時に検出されます。

</critical_requirements>


自動検出: msw、setupWorker、setupServer、msw/browser、msw/node、http.get、http.all、HttpResponse.json、server.use、resetHandlers、onUnhandledRequest、mockServiceWorker.js、delay()

対象:

  • バックエンドがまだない開発時に HTTP レスポンスをモックする
  • アプリケーションコードを変更せずに、空のレスポンス、エラーレスポンス、遅いレスポンスを確認する
  • 開発サーバーとテストスイートで 1 つのハンドラーセットを共有する
  • 1 つのテストだけで単一のエンドポイントをオーバーライドする

対象外:

  • テストランナーの設定とライフサイクルフック — このスキルでは beforeAll と afterEach に何を記述するかを説明しますが、それらを提供するランナーについては扱いません
  • コンポーネントのレンダリングと結果のクエリ
  • 実際のバックエンドとの統合。サーバー自体の動作がテスト対象である場合

MSW がモックするのは、呼び出し元のコードではなくネットワークです。アプリケーションに注入、ラップ、置換されるものはないため、テストで実行されるものは本番環境で実行されるものと同じです。また、同じハンドラーを開発サーバー、テストスイート、デモビルドで使用できます。

これは、ハンドラーセットが個々のテストのニーズではなく、API の説明であり続ける場合に限ります。データはフィクスチャに置き、ハンドラーはフィクスチャを選択します。個々のテストで必要な変更は、後で破棄されるオーバーライドを通じて適用します。


<decision_framework>

応答を切り替える仕組み

実現したいこと使用するもの
複数のテストで共有するシナリオデフォルトの隣にエクスポートする名前付きのバリアントハンドラー
1つのテストだけで異なる内容を確認するserver.use(variant()) — resetHandlers() で破棄されます
開発中に手動で状態を切り替えるデフォルトのハンドラーがリクエスト時に読み取るバリアントマップ
テスト対象のコードが待機する応答明示的な時間を指定した delay(ms)

実行時のバリアント切り替えは開発時のみに使用します。テストでは、状態を設定したテストの後も残る共有の可変状態になります。一方、server.use() はスコープが限定され、自動的にクリーンアップされます。

</decision_framework>


基本パターン

パターン 1:レスポンスボディを専用モジュールに配置する

フィクスチャはハンドラーとは別に配置し、API の生成型に対して型付けします。これにより、1つのボディを複数のハンドラーで使い回せるほか、スキーマの変更が型エラーとして検出されます。

// mocks/features.ts
import type { GetFeaturesResponse } from "./api-types";

export const defaultFeatures: GetFeaturesResponse = {
  features: [{ id: "1", name: "Dark mode", status: "done" }],
};

export const emptyFeatures: GetFeaturesResponse = { features: [] };

1つのテストに固有のデータは、そのテスト内に記述します。

全コード:examples/core.md


パターン 2:バリアントハンドラー

デフォルトのハンドラーと各代替シナリオを1つのモジュールからエクスポートすることで、テストと開発サーバーは名前でバリアントを選択できます。

import { http, HttpResponse } from "msw";

export const getFeaturesHandlers = {
  defaultHandler: () =>
    http.get(ENDPOINT, () => HttpResponse.json(defaultFeatures)),
  emptyHandler: () =>
    http.get(ENDPOINT, () => HttpResponse.json(emptyFeatures)),
  errorHandler: () =>
    http.get(ENDPOINT, () => new HttpResponse("Server error", { status: 500 })),
};

HttpResponse.json は JSON のコンテンツタイプを設定し、init.status で別のステータスが指定されない限り 200 を返すため、エラーのバリアントのみステータスコードを指定します。プレーンテキストや空のエラーなど、JSON 以外のボディには new HttpResponse(body, init) を使用します。

リクエスト時にバリアントマップを読み取るデフォルトのハンドラーを含む全コード:examples/core.md


パターン 3:1つのハンドラーセット、2つのセットアップ

ハンドラーの配列は共有し、それを使用するモジュールは環境に応じて選択します。

// browser-worker.ts
import { setupWorker } from "msw/browser";
export const browserWorker = setupWorker(...handlers);

// server-worker.ts
import { setupServer } from "msw/node";
export const server = setupServer(...handlers);

アプリとの連携を含む全コード:examples/browser.md


パターン 4:テストのライフサイクルとテストごとのオーバーライド

一度リッスンを開始し、テスト間でリセットし、最後に終了します。server.use() はハンドラーを先頭に追加し、そのハンドラーは resetHandlers() によって削除されます。

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

it("renders the empty state", async () => {
  server.use(getFeaturesHandlers.emptyHandler());
  renderApp();
});

全コード:examples/core.md


<red_flags>

注意すべき点

実行時に問題が発生します:

  • ブラウザーバンドルで setupServer を使用する、または Node で setupWorker を使用する — エラーには不足しているグローバルが示されるため、インポートの取り違えではなく環境の問題に見えます
  • worker.start() を await せずに呼び出す — 最初のリクエストでワーカーとの競合が起き、実際のネットワークに到達します。その結果、最初に実行される spec で断続的な失敗が発生します
  • サーバーサイドレンダリングされるエントリーポイントでブラウザーワーカーをトップレベルでインポートする — サービスワーカーのコードがサーバーバンドルに入り、ビルドに失敗します。typeof window チェックの内側で動的にインポートしてください
  • afterEach に resetHandlers() がない — オーバーライドがテスト後も残るため、単独では成功するテストスイートが、異なる順序で実行すると失敗します
  • worker.start() が環境ガードの内側にない — 本番バンドルでサービスワーカーが登録され、実際のユーザーにフィクスチャが提供されます

予想外の動作:

  • 引数なしの delay() はブラウザーではランダムな 100~400ms の待機になり、Node では無効化されます。そのため、一時停止が必要なテストでは時間を明示的に渡す必要があります
  • server.use() によるオーバーライドは resetHandlers() まで残り、追加したテストが終了しても解除されません
  • http.all() はパスに対するすべてのメソッドに一致するため、誤った HTTP メソッドでリクエストを送っても成功応答が返ります
  • once: true を指定すると、ハンドラーは1回のリクエストに一致した後に停止します。これは順次応答に必要な動作ですが、後続のリクエストが気付かれないままフォールスルーする原因にもなります
  • start/listen に onUnhandledRequest を指定しないと、ハンドラーを記述していないリクエストは何も通知されずに通過します。そのため、モックの不足がバックエンドの障害のように見えます。"bypass"、"warn"、"error" のいずれかを指定する必要があり、これによって無通知、警告、致命的エラーのいずれになるかが決まります

</red_flags>

コメント