mcpField
S
service-virtualization

service-virtualization

github.com/petrkindlmannテスト・品質2221.0k 回インストール6 日前に更新
リポジトリ

service-virtualization について

HTTP スタブ、記録再生、障害注入、一時的な実サービスなど、依存関係の分離方法を選ぶためのテストスキルです。HTTP 層でスタブを使い、未処理のリクエストがテストから漏れる場合に検出することを重視します。

主な機能

  1. 01依存関係とテスト段階に応じた分離方式の選択
  2. 02SDK メソッドではなく HTTP 層でのスタブ
  3. 03MSW と WireMock による HTTP モック
  4. 04Testcontainers による実サービスの起動
  5. 05Toxiproxy によるネットワーク障害の注入
  6. 06API 通信の記録と再生

活用シーン

  1. 01実サービスを呼ばずにサードパーティ API のエラーをテスト
  2. 02一時的なデータベースで統合テストを実行
  3. 03タイムアウトや接続リセットへの処理を検証

インストール

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

$npx skills add https://github.com/petrkindlmann/qa-skills --skill service-virtualization

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

同梱ファイル

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

references/
ci.md
msw.md
record-replay.md
testcontainers.md
toxiproxy.md
wiremock.md

詳細

SDK レイヤーで Stripe をモックすると、テストはすべて成功します。しかし、スタブが契約に結び付けられていないため、Stripe がレスポンスのフィールドを変更した途端、本番環境では 500 エラーが発生します。このスキルでは、依存関係ごとに適切な分離戦略(インプロセスモック、HTTP スタブ、記録・再生、フォールトインジェクション、一時的な実サービス)を選び、SDK のアップグレードに左右されないよう HTTP レイヤーでスタブを作成します。また、スタブをすり抜けて実際の呼び出しが発生した場合に、CI の実行が失敗するようにします。

クイックルート

状況参照先
Node/ブラウザーのテストで外部 HTTP API を呼び出すMSW → references/msw.md
複数言語にまたがる CI、複雑なマッチング、またはスタンドアロンのスタブサーバーが必要WireMock → references/wiremock.md
依存関係が DB/キャッシュ/キューTestcontainers → references/testcontainers.md
タイムアウト、レイテンシー、接続リセットをテストするToxiproxy → references/toxiproxy.md
実 API からスタブを作成する、または複数手順のベースラインが必要記録・再生 → references/record-replay.md
これらを GitHub Actions に組み込むreferences/ci.md
どれを選べばよいかわからない判断ツリー(以下)

調査の質問

まず .agents/qa-project-context.md を確認します。ファイルが存在する場合は、その内容に従い、既存のモックの慣例を尊重し、すでに回答されている項目は省略します。その後、次の点を確認します。

  • システムは外部依存関係をいくつ呼び出し、そのうちテストで扱いにくいものはどれですか? レート制限がある、遅い、不安定、または有料の依存関係は、仮想化の優先候補です。決済、メール、認証、サードパーティーのデータソースをリストアップします。
  • どのテストレベルで分離が必要ですか? 単体テストでは高速なインプロセスモック、統合テストでは HTTP レベルのスタブ、E2E では実サービスまたはコンテナー化されたサービスが求められます。テストレベルに応じて戦略を決めます。
  • プロバイダーが公式のテストモードやサンドボックスを提供していますか? Stripe のテストモード、Twilio のテスト用認証情報などは、忠実度の点で自作スタブに勝ります。提供されている場合は、そちらを優先します。
  • これらの依存関係との契約テストがありますか? ある場合は、契約テストによってスタブとの同期を保てます。contract-testing を参照してください。ない場合、所有していないものをモックするのは既知のリスクです(コア原則 3)。
  • CI で Docker を利用できますか? Docker が使えない場合は、MSW(インプロセス)が適しています。Docker があれば、WireMock、Testcontainers、Toxiproxy を利用できます。

基本原則

1. 必要な信頼度に合わせて分離レベルを選びます。 単体テストでは積極的にモックできます。内部ロジックをテストするためです。統合テストでは、境界をテストするため、現実に即したスタブまたは実サービスを使うべきです。E2E では、信頼性を維持できる範囲で本番環境に最も近いものを実行します。

2. 高速、無料、かつ信頼性が高い場合は、フェイクより実サービスが優先です。 ローカルの PostgreSQL コンテナーはフェイクより信頼度が高く、コストもわずかです。そのため、こちらを優先します。フェイクは、遅い、不安定、または有料の依存関係に限定します。

3. 契約なしに自分が所有していないものをモックしてはいけません。プロバイダーのテストモードを優先します。 Stripe をスタブし、Stripe がレスポンスの形式を変更すると、テストは成功したまま本番環境で障害が発生します。推奨順は、プロバイダーの公式テストモード/サンドボックス(Stripe のテストモード、Twilio のテスト用認証情報)→ ドリフトを検証する契約テスト付き HTTP スタブ → よりよい方法がない場合に限り単純なスタブです。contract-testing を参照してください。

4. スタブは現実に即した形で失敗しなければなりません。 常に 200 を返すスタブでは、エラー処理をテストできません。すべてのスタブに、429 のレート制限、500 エラー、タイムアウト、不正な本文などのエラーケースを用意します。

5. テストとツールの間に抽象化レイヤーを 1 つ設けます。 MSW のハンドラー、WireMock のスタブ、Testcontainers のセットアップを一貫したインターフェースの背後にまとめれば、ツールを切り替えてもテストを書き直す必要がありません。


判断フレームワーク

各分離戦略を使うタイミング
戦略速度忠実度複雑さ最適な用途
プロセス内モック最速最低ごく簡単ユニットテスト、内部モジュールの分離
HTTP スタブ(MSW)高速中程度低外部 API を呼び出すフロントエンド/Node テスト
HTTP スタブ(WireMock)高速中〜高中言語非依存の環境、複雑なマッチングルール
記録と再生初回実行後は高速初期は高いが、徐々に低下中実際の API からスタブを素早く作成する
サービスフェイク中程度高高状態を持つ依存関係(インメモリ DB、フェイク認証)
一時的な実サービス(Testcontainers)遅め最高中データベース、メッセージキュー、キャッシュ
共有実サービス遅い本番レベル低(セットアップ時)ステージング検証、デプロイ直前の最終チェック
判断ツリー
Is the dependency internal to your codebase?
├─ Yes → In-process mock (vi.mock / jest.mock / monkeypatch)
└─ No → Is it a database, cache, or message queue?
         ├─ Yes → Testcontainers (ephemeral real instance)
         └─ No → Is it a third-party HTTP API?
                  ├─ Yes → Does the provider offer a test/sandbox mode?
                  │        ├─ Yes → Use sandbox in staging, MSW/WireMock in CI
                  │        └─ No → MSW or WireMock + contract test for drift detection
                  └─ No → Is it an internal microservice?
                           ├─ Yes → Contract test (Pact) + stub for consumer tests
                           └─ No → Evaluate case by case

ツール

プロセス内の Node/ブラウザテストではデフォルトとして MSW、複数言語にまたがる CI やスタンドアロンのスタブサーバーには WireMock、OpenAPI 仕様が契約となる場合は Prism、開発時の探索的なモックには Mockoon を選びます。それぞれの詳細なコードは references/ にあります。以下を参照してください。

MSW(Mock Service Worker)

MSW はブラウザまたは Node で HTTP をインターセプトします。リファレンスは固定バージョンの 2.15.0 API を対象としています。MSW 3 ではインポートとオプションが変更されているため、コピーする前にインストール済みのメジャーバージョンを確認してください。 SDK のメソッドではなく、HTTP レイヤー(POST /v1/payment_intents)でスタブ化してください。そうすることで、テストを SDK のバージョンから切り離せます。

MSW 2 では、対応するハンドラーがないインターセプト済みリクエストを拒否するために、setupServer(...).listen({ onUnhandledRequest: "error" }) を使用してください。想定どおりのモックレスポンスと、意図的な未処理リクエストの拒否の両方を証明してください。明示的なパススルー、捕捉されないトランスポート、または拒否を握りつぶすアプリケーションコードについては、個別に確認する必要があります。テストスイートが成功することや、ログ検索で何も見つからないことは、外部送信がない保証にはなりません。

オプトインのエラーハンドラーは、対応する成功ハンドラーより前に配置してください。状態を持つファクトリーはテストごとに再構築してください。resetHandlers() だけでは、ファクトリーが保持するマップはクリアされません。決済ハンドラー、明示的なローカルフォールバック、状態リセット、タイムアウト/リトライのケースを含む実行可能なサンプルクライアント、および Bearer トークンをキーとする状態を持つ認証フロー(作成/検証/更新/http.delete による失効)については、references/msw.md を参照してください。

WireMock

言語非依存の HTTP スタブサーバーです(現在の安定版は 3.13.2。2026 年半ば時点で 4.0 はベータ版のみのため、CI では 3.x を使用してください)。スタンドアロンまたは Docker コンテナとして実行できます。複数言語が混在する環境や、複雑なマッチングルールに最適です。

エラーシナリオには優先度ベースのマッピングを使用してください。priority: 1 のマッピングは、オプトインヘッダー(X-Test-Scenario: rate-limit)に一致すると 429 を返し、テストで要求された場合にのみデフォルトの正常系マッピングを上書きします。WireMock には、スタブのプログラムによる作成(POST /__admin/mappings)、検証(POST /__admin/requests/count)、リセット(POST /__admin/mappings/reset)を行う管理 API もあります。

Docker のセットアップ、レスポンスをテンプレート化したページネーション対応マッピング、優先度付きエラーマッピングの JSON、および contract-testing とのドリフト検出の接続点(Pact のリプレイ、または Prism による OpenAPI 仕様に対するスタブの検証)については、references/wiremock.md を参照してください。

その他の HTTP モックサーバー
ツール強み使用する場面
Mockoonデスクトップ UI + CLI、OpenAPI インポート、ルールベースのレスポンス、軽量開発時のモック、および CI での簡単な CLI モック
Hoverflyキャプチャとリプレイ(実トラフィックを記録し、決定論的に再生)、キャプチャ/シミュレート/変更/合成モード実際の依存先からモックへの移行 — 一度記録すれば、以後は何度でも再生
PrismOpenAPI 駆動のモックサーバー(Stoplight)、リクエストの検証と仕様に基づくレスポンスの生成仕様を公開している OpenAPI ファーストのプロジェクト
MockServerJava ベース、豊富な期待値マッチング、マルチプロトコルすでに MockServer を利用している JVM チーム
Testcontainers

Docker で実際のサービスを起動して統合テストを実施します。コンテナはテストスイートの開始前に起動し、終了後に破棄されます。@testcontainers/postgresql 11.x などを使用してください。ポートはランダムに割り当てられるため、必ず getMappedPort()/getConnectionUri() で取得し、5432/6379 をハードコードしないでください。

並列で PostgreSQL、Redis、Elasticsearch を起動する際の待機戦略、Vitest の globalSetup の設定(process.env.DATABASE_URL、testTimeout: 30_000)、古くなったイメージタグを更新する際の注意点(elasticsearch:8.12.0 は Elastic 9.x より古い)については、references/testcontainers.md を参照してください。

Toxiproxy(障害注入)

ghcr.io/shopify/toxiproxy:2.12.0 は、アプリと依存サービスの間に TCP プロキシとして配置され、レイテンシ、帯域幅制限、接続リセットを注入します。アプリの接続先には、実際のサービスではなくプロキシのポートを指定してください。afterEach では必ずトキシックを削除してください。削除しないと、テスト間に残ってしまいます。

compose のポートマッピング、createProxy(name, listen, upstream) の設定(compose の 15432/16379 のプロキシポートを実際のアップストリームに対応付ける方法を含む)、すべての呼び出しで response.ok を確認するヘルパー、レイテンシと接続リセットの使用例については、references/toxiproxy.md を参照してください。広範なレジリエンス検証やゲームデイの取り組みについては、chaos-engineering を参照してください。


記録と再生

記録と再生では、実際の API レスポンスを一度記録し、それを決定論的に再生します。スタブの初期構築や、複数ステップのインタラクションのリグレッション基準に適しています。独自実装のレコーダーではなく、記録・再生ライブラリ(Hoverfly、Polly.JS、または VCR 形式のカセット)を使って実装してください。

動的なデータ(タイムスタンプ、UUID)、状態を持つシーケンス、経過時間によって破綻します。記録は数週間で古くなります。必ず recordedAt を記録し、記録から30日を超えた場合はテストを失敗させて、再記録を促してください。

カセット形式、30日間の有効期限を確認する assertFresh()、複数ステップのフロー(注文の作成 → 商品の追加 → クーポンの適用 → 決済)を実行する再生ハーネスについては、references/record-replay.md を参照してください。


CI 統合

MSW には個別のスタブサーバーは不要です。CI では onUnhandledRequest: "error" を設定し、期待したモックレスポンスが確認されたことを検証するとともに、意図的に未処理のリクエストを発生させてガードが機能することを確認してください。想定外のバイパスを記録し、外部への通信を完全に遮断する必要がある場合は、CI のネットワーク制御を使用してください。WireMock と Testcontainers には Docker が必要です。

互換性のない2種類のポートモデルが共存しているため、1つのスイート内で混在させてはいけません。docker-compose モデルでは固定ポートを公開するため、DATABASE_URL=...localhost:5432... のようにハードコードできます。一方、Testcontainers モデルではポートがランダムに割り当てられ、getMappedPort() で取得して process.env に設定します。スイートごとにどちらか一方を選んでください。

MSW のステップ、docker-compose を使う GitHub Actions のジョブ(up -d --wait --wait-timeout 120、if: always() の後処理)、制約ごとのツール一覧表については、references/ci.md を参照してください。


アンチパターン

1. すべてをモックする

依存先をすべてモックすると、テストで検証できるのはモックの動作だけであり、システムが動作するかどうかは確認できません。データベースとキャッシュには Testcontainers を使って実際のサービスを利用し、外部 HTTP API のみスタブ化してください。

2. テスト間でモックの動作に一貫性がない

あるテストでは Stripe を { id: "pi_123" } としてスタブ化し、別のテストでは { paymentIntentId: "pi_123" } としていると、現実の異なるバージョンが2つできてしまいます。ハンドラーを集約し、スイート全体で同じレスポンス形式を再利用してください(references/msw.md の共通形式を参照)。

3. API の変更時にスタブを更新しない

マッピングでは Stripe が { amount: 1000 } を返すことになっていても、実際の API が { amount: 1000, currency: "usd" } を返すようになっている場合があります。テストは通っても、本番環境では失敗します。差異を検出するにはコントラクトテストを使用してください。contract-testing と、references/wiremock.md に記載された差異が生じる境界を参照してください。

4. 間違ったレイヤーをスタブ化する

stripe.paymentIntents.create(SDK メソッド)をモックすると、テストが SDK のバージョンに依存します。HTTP レイヤー(POST /v1/payment_intents)でスタブ化すれば、HTTP クライアントや SDK のバージョンに関係なくテストを実行できます。

5. エラーシナリオの網羅性がない

常に 200 を返すスタブでは、リトライロジック、タイムアウト処理、レート制限時のバックオフ、エラーの解析を一切実行できません。各スタブに、対応するエラーパターンを用意する必要があります。

6. 共有され、長期間稼働するモックサーバー

複数の CI ジョブがアクセスする共有 WireMock インスタンスを使うと、依存関係が生じ、状態が漏えいします。テスト実行ごとに、分離された独自のスタブサーバーを起動します。

7. 有効期限のない記録・再生

6 か月前の記録には、すでに存在しない API が反映されている可能性があります。recordedAt を記録し、記録が 30 日を超えている場合はテストを失敗させて、再記録を促します(references/record-replay.md を参照)。

8. 成功ハンドラーがエラーシナリオを覆い隠す

MSW は、レスポンスを返す最初の一致ハンドラーで処理を停止します。レート制限ハンドラーを決済作成ハンドラーの後に置くと、429 を返す機会がありません。オプトインのエラーを先に配置し、同じエンドポイントに対するエラーリクエストと通常の成功リクエストの両方を検証します。

9. フィクスチャの状態を保持したままのハンドラーのリセット

server.resetHandlers() は実行時のオーバーライドを削除しますが、既存のクロージャーには決済やセッションのマップが残っています。テストごとに状態を持つハンドラーファクトリーを再作成し、以前存在していたレコードがリセット後には存在しないことを検証します。固定された時計のもとで衝突するタイムスタンプではなく、フィクスチャ内で一意の ID を使います。


検証

まず最小のチェックから、実際のコードと観測されたリクエストを検証します。

# In this skill repository: execute the actual MSW reference code fences.
node --test --test-isolation=none tests/examples/msw.test.mjs

回帰テストでは、正常な作成/取得/確定フロー、オプトインの 429、ハンドラーに到達した後のクライアントタイムアウト、上限付きの 503 リトライ、リセット後の新しい状態、フォールバック前に意図的な未処理リクエストが拒否されることを確認する必要があります。レート制限ハンドラーを成功ハンドラーの後ろに移動した場合や、古い状態を保持した場合に、対応するテストが失敗する必要があります。

利用側のプロジェクトでは、CI 設定で実際の統合コマンドを実行し、同じシグナルを検証します。ログにプロバイダーのホスト名がないことから分かるのは、検索で該当行が見つからなかったということだけです。モックされたリクエスト自体が、プロバイダーの URL をログに出力する場合もあります。一致/未処理/バイパスされたリクエストの証拠を調べ、ネットワークの制限を別途確認します。コンテナのフィクスチャでは、正常な起動と確実な終了を検証します。どのトランスポートとシナリオを実行したかを明記します。このレシピのテストでは、実際のプロバイダーとの契約は検証できません。


完了条件

  • 外部依存関係ごとに、依存関係の分離方法が決定され、文書化されています(どれに MSW/WireMock のスタブを使うか、どれに Testcontainers を使うか、どれにプロバイダーのサンドボックスモードを使うか)。
  • 重要な外部依存関係すべてをスタブでカバーし、それぞれに少なくとも 1 つのエラーパス(4xx/5xx、タイムアウト、またはレート制限)があります。
  • スタブとマッピングファイルが、テストコードと同じリポジトリでバージョン管理されています。
  • インストール済みフレームワークの未処理リクエストガードを有効にしてテストスイートが成功し、想定したモックリクエストが観測され、意図的に未処理にしたリクエストによってガードがフォールバックを拒否することが確認されています。予期しないバイパスを把握し、送信先への通信がゼロであるという主張にはネットワーク上の証拠があります。
  • 状態を持つフィクスチャと実行時のオーバーライドがテスト間でリセットされています。リセット前に作成したレコードがリセット後には存在せず、以前のエラーオーバーライドが次のテストに影響しません。
  • 記録・再生のベースラインには recordedAt のタイムスタンプと 30 日の有効期限チェックが設定されており、期限切れの場合はテストが失敗します。

リファレンスファイル(references/ 内)

  • msw.md — 順序付きの支払い/エラーハンドラー、明示的なローカルフォールバックスイッチと状態リセットを備えた Vitest のセットアップ、実行可能なタイムアウト/リトライの例、認証の作成/検証/更新/取り消し。
  • wiremock.md — Docker のセットアップ、レスポンステンプレート対応およびページネーション対応のマッピング、優先度ベースのエラーマッピング、管理 API、契約の乖離を検出する接点(Pact/Prism)。
  • testcontainers.md — PostgreSQL/Redis/Elasticsearch の並列起動、globalSetup の配線、イメージタグの更新に関する注記。
  • toxiproxy.md — Compose のポート、createProxy のアップストリーム配線、response.ok チェックを含むヘルパー、レイテンシとリセットの使用例。
  • record-replay.md — ツールの選択肢、カセット形式、30 日間の assertFresh チェック、複数ステップのリプレイハーネス。
  • ci.md — MSW(インフラ不要)、docker-compose の GitHub Actions ジョブ、2 種類のポートモデル、制約条件別のツール表。

関連スキル

  • contract-testing — Pact/ブローカーを使ったコンシューマー主導の契約検証。スタブが実際のプロバイダーと一致することを証明する場合に参照してください。共有スキーマとの乖離を検出するだけの場合には適していません。
  • test-environments — Docker Compose による環境戦略、プレビュー環境、シードデータ。環境の立ち上げに関するもので、単一の依存関係を分離するためのものではありません。
  • chaos-engineering — 広範なフォールトインジェクションキャンペーン、ゲームデー、影響範囲の制限。テストで特定の依存関係に障害を起こすことではなく、レジリエンスそのものを目的とする場合に参照してください。
  • api-testing — REST/GraphQL のテストパターン、スキーマ検証、認証フローのテスト。
  • test-data-management — スタブ状態を設定するためのファクトリーパターンとデータシーディング。

コメント