contract-testing について
Pactを使うチーム向けに、コンシューマーテスト、プロバイダー検証、デプロイゲートを含むコンシューマー駆動の契約テストを案内します。Pact Brokerとcan-i-deployで互換性を確認し、サービスの個別デプロイを支援します。
主な機能
- 01Pactコンシューマーテストの作成
- 02Broker経由のプロバイダー検証
- 03can-i-deployによる不整合デプロイの遮断
- 04HTTPとメッセージ契約への対応
- 05OpenAPIとPactの併用
活用シーン
- 01CIでAPIフィールド変更の互換性問題を検出
- 02共有統合環境なしでサービスを個別デプロイ
- 03Kafkaやキューメッセージの契約確認
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ petrkindlmann/ qa-skills --skill contract-testingインストール後、Claude Code で/contract-testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/ci-pipelines.mdpact-js-setup.mdschema-first.md詳細
確認事項
まず .agents/qa-project-context.md を確認してください。存在する場合はその内容を使用し、すでに回答されている項目は省略してください。次に、以下を確認します。
- アーキテクチャ: マイクロサービス、コンシューマー(モバイル/SPA)が分離されたモノリス、または BFF パターンのどれですか?チームが個別にデプロイする場合、コントラクトテストが最も重要になります。
- コントラクトの所有者: コンシューマー主導(コンシューマーが必要なものを定義)ですか、それともプロバイダー主導(プロバイダーが仕様を公開)ですか?ほとんどのチームでは、コンシューマー主導が有効です。
- API のバージョニング戦略: URL ベース(
/v1/、/v2/)、ヘッダーベース、またはバージョニングなしのどれですか?コントラクトではバージョンネゴシエーションを考慮する必要があります。 - コンシューマーとプロバイダーのペアはいくつありますか? まず、トラフィックが最も多い連携、または最も壊れやすい連携から始めてください。すべてを一度にコントラクトテストしようとしないでください。
- 既存の API 仕様: OpenAPI/Swagger 仕様はありますか?ある場合は、スキーマファーストのコントラクト(または Schemathesis)を出発点として検討してください。
- HTTP か非同期か: リクエスト/レスポンス API にはここで紹介する HTTP の例を使用します。キュー/トピックとの連携(Kafka、SNS/SQS)には Pact のメッセージコントラクトを使用してください(Pact-JS Setup を参照)。
基本原則
1. コンシューマーが必要なものを定義し、プロバイダーがそれを提供できることを検証します。 コンシューマーは「GET /users/123 を呼び出し、{ id, name, email } を期待します」という内容を宣言するテストを作成します。プロバイダーは、このテストを実際の実装に対して実行します。プロバイダーがコントラクトを満たせない場合、デプロイ前にビルドが失敗します。
2. ブローカーを信頼できる唯一の情報源とします。 ドキュメントでも、Slack のスレッドでも、「とりあえずデプロイして確認」でもありません。Pact と検証結果は Pact Broker に保存されます。プロバイダーの検証では、ローカルファイルではなくブローカーから Pact を取得します(pactBrokerUrl + consumerVersionSelectors)。ブローカーは、どのコンシューマーバージョンが実際に稼働しているかを把握しているためです。
3. コントラクトテストは統合環境の代わりになりますが、統合テストの代わりにはなりません。 複雑な複数ステップのワークフローには、引き続き統合テストが必要です。コントラクトテストにより、インターフェースの検証だけを目的としてコンシューマーとプロバイダーを一緒にデプロイする必要がなくなります。
4. コントラクト違反でビルドを失敗させます。 警告を記録しながらデプロイを許可するコントラクトテストには、何の価値もありません。コントラクトをデプロイゲートにする必要があります。
5. テストするのはコントラクトであり、ビジネスロジックではありません。 コンシューマーテストでは、レスポンスの形式とステータスコードを検証します。プロバイダー検証では、コントラクトを満たせることを確認します。ビジネスルールのテストには、ユニットテストと統合テストを使用してください。
Pact-JS のセットアップ
コンシューマー側とプロバイダー側の両方で、@pact-foundation/pact を開発依存関係としてインストールします。ワークフローは2つのパートで構成されています。
- コンシューマーテスト: コンシューマーがプロバイダーに必要なもの(リクエストの形状と期待するレスポンス)を宣言します。テストを実行すると、
pacts/<consumer>-<provider>.json(コントラクトファイル)が生成されます。コントラクトでは値を厳密に固定するのではなく、型や形式を検証できるように、Matchers(Matchers.like、Matchers.eachLike、Matchers.integer、Matchers.string、Matchers.regex)を使います。 - プロバイダー検証: プロバイダーはモックではなく実際の実装に対してコンシューマーの pact を実行します。
stateHandlersを使って、各given(...)状態で期待されるデータをセットアップし、検証結果をブローカーに公開します。
2026年6月時点の最新バージョンである Pact-JS v16 では、
PactV4はPactに、MatchersV3はMatchersに名前が変更されました。 v16 では古い名前は削除されています。古いブログ記事やサンプルからコピーする場合は、インポートを更新してください。API の動作に変更はありません。
イベント駆動型システムでは、同じ Pact クラスがメッセージ pact(Kafka、SNS/SQS、RabbitMQ)にも対応しています。コンシューマーは期待するメッセージの形状を検証し、プロバイダーはプロデューサーが出力する内容を検証します。
インストールコマンド、コンシューマーテスト(単一ユーザー、404、ページネーション付きリスト)、状態ハンドラーと保留中の pact を使ったブローカー主導のプロバイダー検証スペック、Pact Broker の Docker Compose、メッセージコントラクトに関する案内については、references/pact-js-setup.md を参照してください。
Pact Broker
Pact Broker は、pact ファイルを公開し、プロバイダーの検証結果を記録する中央レジストリです。can-i-deploy ワークフローを実行できます。Postgres をバックエンドとして Docker Compose でローカルに起動します。コンシューマーの CI では、コミット SHA とブランチのタグを付けて pact を公開します。
すべての認証情報は環境変数から渡してください。Postgres のパスワード、ブローカーの DB URL、基本認証のパスワードが対象です。いずれかを compose ファイルにハードコードすると、バージョン管理にシークレットが漏えいします。ブローカーのイメージは :latest ではなく、リリース済みのタグに固定してください。
docker-compose.pact-broker.yml と pact-broker publish コマンドについては、references/pact-js-setup.md を参照してください。
コンシューマー主導のワークフロー
一連の流れは次のとおりです。
1. Consumer writes contract test
└── Generates pact JSON file
2. Consumer CI publishes pact to broker
└── Broker stores pact tagged with consumer version + branch
3. Broker webhook triggers provider verification
└── Provider CI pulls latest pact, runs verification
4. Provider publishes verification result to broker
└── Broker records: "provider v2.3.1 satisfies consumer v1.5.0"
5. Before deploy: can-i-deploy check
└── "Can consumer v1.5.0 be deployed? Yes, provider v2.3.1 is in production and verified."
どちらのパイプラインもコントラクトテストを実行し、結果をブローカーに公開し、can-i-deploy によってデプロイを制御します。プロバイダーのパイプラインでは repository_dispatch イベントも監視するため、新しい pact が公開されると自動的に検証が実行されます。
コンシューマーの CI ワークフロー、プロバイダーの CI ワークフロー(Postgres サービスとマイグレーションを含む)、および単独で実行する can-i-deploy/record-deployment コマンドについては、references/ci-pipelines.md を参照してください。
Pact Broker Webhook
Pact Broker で Webhook を設定し、新しい pact が公開されたときに repository_dispatch 経由でプロバイダーの検証を実行します。Webhook はイベントタイプ pact-changed を指定して https://api.github.com/repos/myorg/user-service/dispatches に POST を送信します。プロバイダーのパイプラインはこのイベントを監視します(references/ci-pipelines.md の repository_dispatch トリガーを参照してください)。
保留中の Pact(段階的な導入)
プロバイダーの Verifier で enablePending: true(および includeWipPactsSince)を設定すると、新しいコンシューマーインタラクションを追加してもプロバイダーのビルドが失敗しません。結果は報告されますが、コンシューマーが期待するものとしてマークするまでは失敗になりません。これはコントラクトを段階的に追加する際の標準的なセーフティネットです。
スキーマファーストとコンシューマーファースト
コンシューマーファースト(Pact)
コンシューマーが必要なものを定義し、実際の利用パターンからコントラクトが生まれます。
最適な対象: コンシューマーによってニーズが異なるチーム(モバイルでは Web より少ないフィールドが必要な場合など)、自然に進化する API、マイクロサービスのエコシステム。
スキーマファースト(OpenAPI + 検証)
プロバイダーが OpenAPI 仕様を公開し、コンシューマーは仕様に照らして利用方法を検証します。
最適な用途: 多数のコンシューマーがいるパブリック API、実装前に設計される API、API 設計のガバナンスが強固なチーム。
OpenAPI 3.0 はプレーンな JSON Schema ではありません(
nullable: trueなど)。vanilla Ajv はデフォルトで draft 2020-12 を使用するため、実際の 3.0 仕様を誤って検証します。ajv-formatsを使って Ajv を OpenAPI の方言に合わせて設定するか、OpenAPI 対応のバリデーターを使用してください。Ajv の設定と OpenAPI 仕様に対する検証ヘルパーについては、references/schema-first.mdの注意事項をご覧ください。
ハイブリッドアプローチ
OpenAPI を設計成果物として使い、Pact を適用の仕組みとして使用します。
- OpenAPI 仕様で API を設計します(プロバイダーチームが設計を主導します)。
- OpenAPI 仕様から Pact コンシューマーテストを生成し、ベースラインとして使用します。
- コンシューマーは、ベースラインを超える具体的なインタラクションを追加します。
- プロバイダーは Pact コントラクト(OpenAPI 仕様の一部)に対して検証します。
双方向コントラクト(PactFlow / SmartBear)
PactFlow(SmartBear 提供)は、コンシューマーの Pact とプロバイダーの検証を切り離す双方向コントラクトテストを提供します。プロバイダーが OpenAPI 仕様を提供し、コンシューマーが Pact を提供すると、PactFlow が互換性を確認します。プロバイダーが Pact の検証を実行する必要はありません。これは有料の PactFlow/SmartBear 機能であり、Pact OSS には含まれません。次のような場合に役立ちます。
- プロバイダーチームが CI で Pact 検証ツールを実行できない、または実行する予定がない場合。
- プロバイダーがすでに OpenAPI 仕様を信頼できる唯一の情報源として公開している場合。
- コンシューマーとプロバイダーのリポジトリを密接に結び付けずに、コントラクトのカバレッジを確保したい場合。
トレードオフ:双方向チェックは、完全な Pact 検証よりも大まかな確認です。実行時の動作を厳密に検証するのではなく、仕様とコントラクトの重なりを検証します。まずは導入の足がかりとして使用し、両チームの合意が得られたら完全な検証に移行してください。
Schemathesis(プロパティベース、仕様駆動)
OpenAPI ファーストのプロジェクトでは、Schemathesis(v4.x) を使って、仕様から直接ライブ API に対するプロパティベーステストを実行できます。何千もの有効および無効なリクエストを生成し、レスポンスが仕様に準拠しているかを確認します。Pact とは異なる種類のバグ(エンコーディング、エッジケースのペイロード、ステータスコードのずれ)を検出できます。Pact はコンシューマー駆動の インタラクション に、Schemathesis は仕様駆動の カバレッジ に使用し、併用してください。CI では、生のシェルコマンドより schemathesis/action@v3 Action を推奨します。
避けてください:
schemathesis run --base-url ... --hypothesis-deadline=2000(Schemathesis ≤ v3。v4.0(2025-06)以降は廃止)。 v4 では--hypothesis-deadlineが削除され、--base-urlは--urlに変更されました。また、スキーマは位置引数になりました。現在の形式:schemathesis run ./openapi.yaml --url <base> --checks all。詳細はreferences/schema-first.mdをご覧ください。
can-i-deploy
can-i-deploy コマンドはデプロイのゲートです。Pact Broker のマトリックスを確認し、「Broker が把握している情報に基づいて、この特定のバージョンはターゲット環境にすでにあるバージョンと互換性があるか」を判定します。デプロイに成功したら、record-deployment で記録し、マトリックスの正確性を保ってください。
必ず --retry-while-unknown <n> --retry-interval <s> を渡してください。これにより、実環境で最もよくある失敗を防げます。コンシューマーが Pact を公開した直後で、プロバイダーの検証がまだ完了していない場合、リトライがなければ結果が得られるのを待たずに競合状態でゲートが失敗します。
can-i-deploy のチェックに合格せずにデプロイしてはいけません。また、main でこのチェックを省略してはいけません。 本番環境に反映されるのは main です。そこでゲートを省略すると、Broker が互換性を確認していないバージョンがリリースされます。これはまさに、コントラクトテストで防ぐべき問題です。
注釈付きの出力とリトライフラグを含む can-i-deploy および record-deployment コマンドについては、references/ci-pipelines.md をご覧ください。
アンチパターン
コントラクトでビジネスロジックをテストする。 コントラクトは薄く保ちます。ステータスコード、フィールドの有無、フィールドの型、フィールドの形式を確認します。ビジネスロジックはユニットテストと統合テストでテストします。
コンシューマーの意見を取り入れず、プロバイダー主導でコントラクトを定義する。 プロバイダーチームだけでコントラクトを定義すると、コンシューマーが実際に必要としているものではなく、必要だとプロバイダーが考えるものをテストすることになります。コンシューマー駆動のコントラクトなら、実際の統合時の不具合を検出できます。
プロバイダーの状態を設定しない。 コンシューマーが given("user 123 exists") を期待しているのに、空のデータベースに対してプロバイダーの検証を実行した場合、その検証に意味はありません。プロバイダーの状態ハンドラーで、正確なシナリオをセットアップする必要があります。
本番 CI でローカルの pact ファイルを使って検証する。 ローカルの pactUrls による検証で確認できるのは、ディスク上の pact だけであり、デプロイ済みのものは確認できません。pactBrokerUrl と consumerVersionSelectors を使ってブローカーから取得し、検証が稼働中のコンシューマーバージョンを反映するようにします。
ローカルマシンから pact を公開する。 pact は、既知のコミット SHA とブランチを使って CI から公開する必要があります。ローカルから公開すると、追跡できないバージョンが生成され、ブローカーが不要なデータで埋まります。
can-i-deploy の失敗を無視する。 can-i-deploy が「不可」と示した場合は、コントラクト違反を修正するか、コンシューマーチームと変更について調整します。それでもデプロイすると、本番環境が壊れます。
すべてのエンドポイントを含む巨大な pact を作る。 重要な統合ポイントから始めます。失敗の発生に応じて、保留中の pact を使いながらコントラクトを段階的に追加します。500 件のインタラクションを含む pact は保守できません。
古い pact を削除しない。 どの環境にもデプロイされていない、90 日より古い pact バージョンを削除するように Pact Broker を設定します。古い pact は検証を遅らせ、マトリックスを分かりにくくします。
検証
成果物が機能することを、小さな確認から順に確かめます。
- コンシューマーテストで pact が生成される。
npm run test:contractを実行し、pacts/<consumer>-<provider>.jsonが書き込まれ、宣言したインタラクションが含まれていることを確認します。ファイルがなければ、コントラクトもありません。 - 実際のサービスに対するプロバイダー検証が成功する。 テストデータベースを起動した状態で
npm run test:contract:providerを実行します。すべてのコンシューマーインタラクションが、モックではなく、稼働中のプロバイダーに対して正常に検証される必要があります。 - ブローカーとの往復を確認する。
pact-broker publish ./pacts --consumer-app-version=$GIT_COMMIT --branch=$GIT_BRANCHで公開し、検証結果が記録された状態で pact がブローカーの UI に表示されることを確認します。 - デプロイゲートを確認する。
pact-broker can-i-deploy --pacticipant=<name> --version=<sha> --to-environment=production --dry-runを実行し、正常なバージョンについて、確定した「可」または「不可」(「不明」ではない)が返ることを確認します。
完了条件
- コンシューマーの pact テストが CI で実行され、実行のたびに
pacts/*.jsonファイルが生成され、コミット SHA とブランチのタグを付けてブローカーに公開される。 - プロバイダーの検証ジョブが、プロバイダーの変更ごと、および新しい pact の公開ごと(Pact Broker の
repository_dispatchWebhook 経由)に CI で実行される。ローカルファイルではなく、ブローカーから pact を取得する。 can-i-deploy(--retry-while-unknownを指定)が、main上のコンシューマーとプロバイダー両方のパイプラインでデプロイを制御し、コントラクトが壊れた場合はジョブを失敗させる。- 各コンシューマーとプロバイダーのインタラクションについて、担当者とレビュー担当者を記載した
CONTRACTS.md(またはCODEOWNERSのエントリー)が存在する。 - 少なくとも 1 つの破壊的変更シナリオをエンドツーエンドで実行し、本番環境に到達する前に
can-i-deployのチェックで検出されることを確認している。
参照ファイル(references/ 内)
- pact-js-setup.md — インストールコマンド、コンシューマーの pact テスト(ユーザー/404/ページネーション)、状態ハンドラーと保留中の pact を使ったブローカー主導のプロバイダー検証、メッセージコントラクトへの参照、Pact Broker の Docker Compose と公開コマンド。
- ci-pipelines.md — コンシューマーとプロバイダーの GitHub Actions ワークフロー、および単独で実行する
can-i-deploy(リトライフラグ付き)/record-deploymentコマンド。 - schema-first.md — OpenAPI に対する Ajv レスポンス検証ヘルパー(3.0 の方言に関する注意事項付き)と、Schemathesis v4 のコマンドおよびアクション。
関連スキル
- api-testing — 独自の REST/GraphQL エンドポイント(形式、ステータス、認証、ヘッダー)の検証に使います。一般的なエンドポイントのテストについてはそちらを、別のチームが API を利用し、共有スキーマだけでなく互換性を保証する必要がある場合はこのスキルを使います。
- service-virtualization — テストを分離するための依存関係のスタブ化やモック化に使います。サービスを偽物に置き換える場合はそちらを、2 つの実際のサービスがインターフェースに合意していることを証明する場合はこのスキルを使います。コントラクトは互換性を検証し、仮想化はそれを偽装します。
- ci-cd-integration — パイプラインの仕組み(これらのコントラクトジョブを CI ゲートとして実行する方法、シークレット、並列化)を扱います。このスキルのデプロイゲートが組み込まれるパイプラインの設定についてはそちらを使います。
- test-environments — 環境戦略を扱います。コントラクト検証をどこで実行するか、ステージング/本番環境全体でブローカーをどのようにプロビジョニングするかを決める場合はそちらを使います。