contract-testing-ts について
contract-testing-tsは、TypeScriptプロジェクトで宣言的なコントラクトテストを設定、作成するスキルです。Vitestで`*.contract.yaml`を使い、関数、クラス、オブジェクトをテストし、結果やモックも検証します。
主な機能
- 01Vitestのコントラクト検出設定
- 02エクスポート項目のYAMLコントラクト作成
- 03入力とエラーのケースを網羅
- 04モック依存先の条件確認
- 05コントラクトテストの実行と検証
活用シーン
- 01パッケージにコントラクトテストを導入
- 02関数やクラスのコントラクトを作成
- 03境界条件やエラー動作をテスト
- 04モックを使うコントラクトを検証
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ beecode-rs/ msh-test-contractor --skill contract-testing-tsインストール後、Claude Code で/contract-testing-tsを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
references/contract-structure.mdexamples.mdmocking.mdspecial-objects.mdterm-types.md詳細
Test Contractor — コントラクトベースのテスト
コントラクトベースのテストフレームワークである test-contractor ライブラリを扱います。*.contract.yaml ファイルでテストケースを宣言的に定義します。主に次の 2 つのワークフローに対応します。
- プロジェクトのセットアップ — Vitest 経由で
*.contract.yamlファイルを検出して実行できるよう、パッケージを設定します - コントラクトの作成 — 関数、クラス、オブジェクトのコントラクト YAML ファイルを作成、保守します
どちらのワークフローでも、コントラクトテストを実行して作業を検証します。
ワークフローの選び方
ユーザーの依頼内容に注意してください。
- 「コントラクトテストをセットアップして」、「コントラクトを設定して」、「test:contract スクリプトを追加して」、「このプロジェクトでコントラクトテストを使いたい」 → プロジェクトのセットアップに進みます
- 「テストを書いて…」、「コントラクトを書いて…」、「このファイルにコントラクトを追加して」、またはソースファイルが指定されている → コントラクトの作成に進みます
- 「これらのファイルのコントラクトを書いて、プロジェクトがセットアップ済みであることも確認して」 → 両方を行い、セットアップから始めます
- 依頼が曖昧な場合は、ユーザーに必要な内容を尋ねます
質問する
不明点がある場合は、作業を進める前に質問してください。質問が必要となる一般的な状況は次のとおりです。
- ユーザーがファイルを指定しているものの、どのエクスポートをテストするかが不明 → 対象にしたい関数やクラスを尋ねます
- ソースファイルに複雑な依存関係があり、どれをモック化すべきかが不明 → 分離したい依存関係を尋ねます
- ユーザーがクラスのコントラクトを求めているものの、コンストラクターのシナリオについて言及していない → コンストラクターの条件も必要かどうかを尋ねます
moduleのパス候補が複数ある(たとえば、異なる場所からファイルをインポートできる) → 正しいパスを確認します- ユーザーが一般的に「テスト」とだけ述べている →
contract.yamlと Vitest の単体テストのどちらを指すのか確認します
コードを読めば判断できることは質問しないでください。判断によって出力が実際に変わる場合にのみ質問してください。
プロジェクトのセットアップ
これはパッケージごとに一度だけ行うセットアップです。完了後は、npm run test:contract ですべての *.contract.yaml ファイルが自動的に検出、実行されます。
ステップ 1:セットアップ済みか確認する
変更を加える前に、プロジェクトに次の項目がすでにあるか確認してください。
vitest.config.contract.tsファイルpackage.jsonのtest:contractスクリプト
両方が存在する場合、プロジェクトはすでに設定済みです。コントラクトの作成に進むか、ユーザーに必要な内容を尋ねてください。
ステップ 2:vitest.config.contract.ts を作成する
プロジェクトのルート(package.json と同じディレクトリ)に配置します。
import { ContractReporter } from '@beecode/msh-test-contractor/contract-reporter'
import { contractYamlPlugin } from '@beecode/msh-test-contractor/vitest-plugin'
import tsconfigPaths from 'vite-tsconfig-paths'
import { coverageConfigDefaults, defineConfig } from 'vitest/config'
export default defineConfig({
plugins: [tsconfigPaths(), contractYamlPlugin()],
test: {
coverage: {
exclude: ['lib/**', 'src/index.ts', 'src/**/__fixtures__/**', ...coverageConfigDefaults.exclude],
},
exclude: ['src/**/__fixtures__/**'],
include: ['src/**/*.contract.yaml'],
mockReset: true,
passWithNoTests: true,
reporters: [new ContractReporter()],
setupFiles: ['./src/__tests__/index-vitest-setup.ts'],
watch: false,
},
})
プロジェクトの構成が慣例と異なる場合は、パスを調整してください。主なフィールドは次のとおりです。
include—*.contract.yamlファイルを検出するための glob パターンです。コントラクトが別の場所にある場合は変更してください。setupFiles— 任意です。テスト環境をセットアップするファイルを指定します(例:process.env.TZ = 'utc')。不要な場合は削除してください。ContractReporter— コントラクトテストの出力をひと目でわかりやすく表示します。Vitest のデフォルト形式を使用する場合は、代わりに'verbose'を指定できます。
プロジェクトがまだ vite-tsconfig-paths に依存していない場合は、インストールが必要です。
npm install --save-dev vite-tsconfig-paths
ステップ 3:package.json に test:contract スクリプトを追加する
{
"scripts": {
"test:contract": "vitest --config=./vitest.config.contract.ts"
}
}
複数のテストスイートを実行する test スクリプトがすでにある場合(例:"test": "concurrently --group 'npm:test:*'")、test:contract スクリプトは自動的に実行対象になります。
ステップ 4:セットアップを確認する
npm run test:contract
コントラクトファイルがまだない場合は、「no test files found」と表示されます。これは想定どおりです。セットアップは完了です。
コントラクトの作成
ワークフローの概要
- ユーザーがテストしたいソースファイルを読みます
- エクスポートとそのシグネチャを特定します
- ソースファイルと同じ場所に
*.contract.yamlファイルを作成します - モック化された依存関係に不足している条件がないか確認します(モック化された依存関係の確認を参照)
- コントラクトを実行して、成功することを確認します
- 失敗があれば修正して、再実行します
リファレンスファイルを読むタイミング
必要に応じて、以下のファイルを読んでください。
| ファイル | 読むタイミング |
|---|---|
references/contract-structure.md | 常に — 中核となる構造、必須フィールド、対象の種類を確認するとき |
references/term-types.md | プリミティブ、オブジェクト、配列、エラーを使ってパラメーター/結果を記述するとき |
references/special-objects.md | !undefined、new Error(...)、new Date(...)、Promise.resolve(...)、__fn__、__class_ref:、__import:を使うとき |
references/mocking.md | 対象にモックが必要な依存関係があるとき |
references/examples.md | ソースとコントラクトのペアを含む、最初から最後までの例を確認したいとき |
まず必ずreferences/contract-structure.mdを読んでください。必須フィールドと規約が定義されています。ほかのファイルは、状況に応じて読んでください。
コントラクトファイルの配置
*.contract.yamlファイルは、ソースファイルと同じディレクトリに配置してください。moduleフィールドには、.tsソースではなく、コンパイル後の.jsファイルを指定する必要があります。
src/
user-service.ts ← source
user-service.contract.yaml ← contract (module: ./user-service.js)
用語設計の方針:ゼロ、1つ、複数、大量、問題
メソッドの用語を記述するときは、以下の5つのシナリオを順番に検討してください。メソッドごとに3〜7個の用語を目指します。
| シナリオ | テストする内容 | 例 |
|---|---|---|
| ゼロ | 入力が空、または欠落している場合。何もないとどうなるか? | []、''、null、{}、0 |
| 1つ | 単一の項目。最も単純な、意味のあるケース。 | [42]、['alice']、{ id: 1 } |
| 複数 | 少数の項目。典型的な正常系のケース。 | [1, 2, 3]、要素が3〜5個の配列 |
| 大量 | 大きな入力。規模が大きくなると挙動が変わるか? | 要素が1000個の配列、非常に長い文字列 |
| 問題 | 何かがうまくいかない場合。無効な入力、境界ケース、エラー。 | 負のID、不正な形式のオブジェクト、誤った型 |
すべてのメソッドで5つすべてが必要なわけではありません。該当しないシナリオは省略してください。ソースコードを読んだら、エクスポートされた各メソッドについて、次の点を自問してください。
- この関数にとって「何もない」とはどのような状態か? → ゼロ
- 意味のある最も単純な入力は何か? → 1つ
- 通常の典型的な呼び出しはどのようなものか? → 複数
- 規模が大きくなると関数の挙動は変わるか? → 大量(該当しない場合は省略)
- 何が問題になりうるか? → 問題(エラー、境界ケース、境界値)
次に、該当する各シナリオの用語を記述してください。
モック化された依存関係の確認
mockを使って別のユニットのコントラクトを参照するコントラクトを記述する場合、参照先のコントラクトが、対象が実際に行う呼び出しを網羅していることを確認する必要があります。これは、対象がモック化されたコントラクトのどの条件にも一致しない引数で依存関係を呼び出すと、モックが返す値を判断できず、テストが失敗したり、予期しない動作をしたりするため重要です。
確認手順:
- コントラクトを記述した後、対象のソースコードを読み、モック化された依存関係への呼び出しをすべて確認します
- 呼び出しごとに、モック化された依存関係のコントラクトに、対象が渡す引数と一致する
paramsを持つ条件があるか確認します - ソースコードに呼び出しがあるのに、モック化されたコントラクトに一致する条件がない場合は、不足している条件を追加します
- 更新後、モック化された依存関係自体のコントラクトテストを再実行し、新しい条件が有効であることを確認します
例: orderService.createOrder({ item: 'book', price: 29.99 })が内部でlogger.debug('Creating order: book')を呼び出す場合、logger.contract.yamlには、params: ['Creating order: book'](またはその呼び出しに一致するパターン)を含むdebugの条件が必要です。この条件がなければ追加してください。
この確認により、コントラクト単体では成功しても、別のコントラクトがモックとして使おうとすると失敗するという、よくある問題を防げます。
コントラクトの検証
コントラクトを記述または更新した後は、必ず実行してください。
npm run test:contract
条件が失敗した場合:
- エラーメッセージを注意深く読みます。失敗した条件と、実際の結果と期待された結果が示されています
paramsが関数に実際に渡される値と一致しているか確認しますresultが関数から実際に返される値と一致しているか確認します- モックを使っている場合、モックの条件が対象の呼び出しをすべて網羅しているか確認します
- 条件を修正して、再実行します
モック化された依存関係のコントラクトを更新した場合は、そのコントラクトテストも実行してください。
npx vitest --config=./vitest.config.contract.ts path/to/the-updated.contract.yaml
最小テンプレート
subject: myFunction # exported name
module: ./my-module.js # path to compiled JS
subjectType: function # or "class"
methods:
__self__: # for standalone functions; use method name for objects
terms:
- params: [input]
result: expected
Vitestへのフォールバック
Contract.yamlは、ほとんどのテストシナリオをカバーします。宣言的に表現できないケースに限り、Vitestの*.test.tsを使用してください。
- 内部呼び出しの順序や回数を検証する場合(例:「ヘルパーがちょうど2回呼び出された」)
- 静的に設定できない実行時のセットアップ/ティアダウン(例:一時ファイル、ネットワークサーバー)
- 非決定的な状態(例:コントラクト経由ではモックできないタイムスタンプ)
- 複雑な非同期パターン(遅延を伴う再試行、競合状態、ストリーミング)
このようなケースに該当する場合は、そのケースに限ってVitestテストを記述し、残りはcontract.yamlに記述します。先頭にコメントを追加してください。
// Supplements: ./my-module.contract.yaml
// Covers: [what's tested here and why contract.yaml couldn't handle it]
クイックリファレンス
- ファイル名:
*.contract.yaml(例:user-service.contract.yaml) subject: モジュール内でエクスポートされている名前をそのまま指定しますmodule: コンパイル済みの.jsファイルへの相対パス(例:./user-service.js)__self__: スタンドアロン関数のメソッド名constructor+constructorParams: クラスを対象とする場合に指定します- 各条件: 入力は
params、出力はresultまたはerror - 自動検出: Viteプラグインがすべての
*.contract.yamlファイルを検出するため、エントリーの.test.tsは不要です - 実行:
npm run test:contract(セットアップが必要です) - 条件の戦略: ゼロ、1つ、多数、大量、例外