vitest-best-practices について
保守しやすい Vitest テストを書くための開発者向けガイドです。明確さ、分離性、実行性能に関するルールを、テーマ別の資料で確認できます。
主な機能
- 01テスト構成と命名の指針
- 02Arrange–Act–Assert の構造
- 03it.each() によるパラメーター化テスト
- 04アサーションとテストダブル
- 05非同期、性能、スナップショットの指針
活用シーン
- 01明確な Vitest テストスイートの設計
- 02モック漏れや実行順依存の不具合の防止
- 03外部依存に使うテストダブルの選択
インストール
コマンドをコピーしてターミナルで実行します。
$
npx skills add https:/ / github.com/ gohypergiant/ agent-skills --skill vitest-best-practicesインストール後、Claude Code で/vitest-best-practicesを入力して読み込まれたか確認します。
詳細
Vitest のベストプラクティス
保守しやすく効果的な Vitest テストを作成するための包括的なパターンです。テストの構成、明確さ、パフォーマンスに関するエキスパートレベルの指針に焦点を当てています。
Vitest テストを作成する際に絶対にしてはいけないこと
- グローバルなモックのクリーンアップ設定を絶対に省略しないでください - 手動でのクリーンアップは安全に見えますが、「離れた場所での作用」による失敗を引き起こします。テストファイル A のモックが 3 ファイル後に実行されるテストファイル B に漏れ、特定の順序でテストを実行したときにしか現れない非決定的な失敗の原因になります。こうしたハイゼンバグのデバッグには、CI で何時間もかかります。
vitest.config.tsで一度clearMocks: true、resetMocks: true、restoreMocks: trueを設定し、実行順序に依存するこの種の失敗を根本からなくしてください。 - describe ブロックを 2 階層を超えてネストしないでください - 深いネストは認知的な負荷と過剰なインデントを生みます。コンテキストはテスト名に含めてください。
describe('when cart is empty', () => describe('addItem', ...))よりも、it('should add item to empty cart')のようにします。 - 独自の純粋関数をモックしないでください - 内部コードをモックすると、テストは壊れやすくなり、価値も下がります。モックするのは外部依存関係(API、データベース、サードパーティライブラリ)だけにしてください。フェイク > スタブ > スパイ > モックの順に優先してください。
toBeTruthy()やtoBeDefined()のような緩いアサーションを使わないでください - こうしたアサーションは、意図していない複数の異なる値でも成功します。toBeTruthy()は1、"false"、[]、{}に対して成功しますが、これらはすべて意味的に異なります。リファクタリングによってgetUser()の戻り値が{id: 1}から1に変わっても、テストは成功したまま、本番コードが壊れてしまいます。緩いアサーションは、本番環境で消え去る誤った安心感を生みます。toBeTypeOf()は緩いアサーションではありません。- 動作ではなく実装の詳細をテストしないでください - 「関数 X が 3 回呼び出された」ことを検証するテストは、誤った失敗を引き起こします。メモ化によって X の呼び出し回数を 1 回に最適化すると、ユーザー体験は同じ(しかも高速)なのに、すべてのテストが失敗します。こうしたテストは、パフォーマンス改善やリファクタリングを積極的に妨げます。コードが内部でどう実現しているかではなく、ユーザーが観察すること(入力に対する出力)をテストしてください。
- テスト間で変更可能な状態を共有しないでください - 実行順序や前のテストの状態に依存するテストは、不安定で信頼性の低いテストスイートを生みます。各テストは、新しいセットアップを用意して完全に独立させてください。
- テストファイルで
anyを使ったり、型チェックを省略したりしないでください - 実装のシグネチャが変わったとき、as anyを使ったテストは、誤った引数で関数を呼び出していても気付かず成功します。TypeScript なら検出できたはずの不具合を含むコードをリリースすることになります。テストは実行可能なドキュメントです。user as anyからは何も伝わりませんが、createTestUser(Partial<User>)なら、そのテストケースで重要なプロパティが正確に分かります。
テストを書く前に考えること
テストを実装する前に、以下のエキスパート向けの考え方を取り入れてください。
テストの分離とセットアップ
- クリーンアップのロジックはどこに置くべきですか? レイヤーごとに考えてください。設定はエラーの種類そのものをなくし(
vitest.config.tsでモックをクリーンアップ)、セットアップファイルはプロジェクト全体に関わる処理(カスタムマッチャー、グローバルモック)を扱い、beforeEachはテスト固有の状態を扱います。各テストで個別にモックをクリーンアップするのは、各関数で独自に null チェックを行うようなものです。動作はしますが、本質を見落としています。懸念事項は、適切な範囲で最も上位のレイヤーに配置してください。 - このテストは前のテストや共有状態に依存していますか? テストスイートは互いに独立した世界です。各テストは、最初に実行されても、最後に実行されても、単独で実行されても、同じように動作する必要があります。状態への依存は、実行順序によって成功したり失敗したりする「量子テスト」を生みます。あるテストが別のテストのデータを必要とするなら、それらは実際には、人工的に分割された 1 つのテストです。
テスト対象
- 動作と実装のどちらをテストしていますか? コードがどのように実現しているか(どの関数が呼び出されたか)ではなく、ユーザーが体験すること(入力 → 出力)をテストします。安全なリファクタリングでも、実装のテストは壊れてしまいます。
- 使える最もシンプルな依存先は何ですか? 実際の実装 > フェイク > スタブ > スパイ > モック。この階層を下がるほど、テストは脆くなります。実際のコードを使うのが現実的でない場合(外部 API、時間のかかる処理)に限り、モックを使います。
テストの明確さ
- このテストを5秒で理解できますか? AAA パターン(Arrange、Act、Assert)に従い、境界を明確にします。セットアップが複雑な場合は、説明的な名前を付けたヘルパー関数に切り出します。
- 同じ動作に複数のバリエーションがありますか? テスト構造をコピーするのではなく、パラメーター化テストに
it.each()を使います。1つの概念につき1つのアサーションにすると、テストに焦点を絞れます。
パフォーマンスとメンテナンス
- 6か月後もこのテストは価値がありますか? フレームワーク内部や些細な操作のテストは避けます。実際にバグを防ぐビジネスロジック、エッジケース、エラー処理に焦点を当てます。
- 保存するたびに実行できるほど、このテストは高速ですか? テストで時間のかかる操作は避けます。データベースにはフェイクを使い、待機時間にはタイマーをモックし、外部呼び出しにはスタブを使います。テストはミリ秒単位で完了する必要があります。
このスキルで扱う内容
vitest のテストパターンに関する専門的なガイダンス:
- 構成 - ファイルの配置、命名、describe ブロックの構造
- AAA パターン - 明確さを即座に得るための Arrange、Act、Assert
- パラメーター化テスト - 重複を減らすための
it.each()の使い方 - エラー処理 - 例外、エッジケース、フォールトインジェクションのテスト
- アサーション - 意図しない値を検出する厳密なアサーション
- テストダブル - フェイク、スタブ、モック、スパイの階層とそれぞれの使いどころ
- 非同期テスト - Promise、async/await、タイマー、並行テスト
- パフォーマンス - 効率的なセットアップとグローバル設定による高速なテスト
- Vitest の機能 - カバレッジ、ウォッチモード、セットアップファイル、設定の検出
- スナップショットテスト - スナップショットがメンテナンス性に役立つ場合と損なう場合
使用方法
このスキルは、コンテキストの使用量を最小限に抑えるため、段階的開示の構成を採用しています。
1. 概要から始める(AGENTS.md)
AGENTS.md を読み、すべてのルールの簡潔な概要(各ルールの1行要約を含む)と、既存のテスト設定を確認するワークフローを把握します。
2. 既存のテスト設定を確認する
テストを書く前に:
- まず
vitest.config.tsでグローバルなモックのクリーンアップ設定(clearMocks、resetMocks、restoreMocks)を確認します。 - 次にセットアップファイル(
test/setup.ts、vitest.setup.tsなど)を探し、その設定を分析します。 - AGENTS.md のワークフローを参照してください。
3. 必要に応じて特定のルールを読み込む
次の明示的なトリガーを参考に、各リファレンスファイルを読み込むタイミングを判断してください。
必ず読み込む(ファイル全体を読み込む):
- Promise/タイマーを使った非同期テストを書く場合 → async-testing.md
- モック、スタブ、スパイ、またはフェイクを扱う場合 → test-doubles.md
次のパターンが見られた場合に読み込む:
- ネストした describe ブロックが 2 階層を超える場合 → organization.md
- テストファイルが実装と同じ場所にない場合 → organization.md
- 明確な Arrange/Act/Assert 構成になっていないテスト → aaa-pattern.md
- わずかな違いしかない重複したテストコード → parameterized-tests.md
- エラーケースのテストがない、またはエッジケースのカバレッジが不十分な場合 → error-handling.md
toBeTruthy()やtoBeDefined()のような緩いアサーション → assertions.md- テストの実行が遅い場合(1 テストあたり 100ms 超) → performance.md
- カバレッジ、ウォッチモード、または Vitest 固有の機能が必要な場合 → vitest-features.md
- スナップショットテストを検討またはレビューする場合 → snapshot-testing.md
特に必要な場合を除き、読み込まない:
- テストが速い場合(50ms 未満)、performance.md は読み込まないでください
- スナップショットについて言及されていない限り、snapshot-testing.md は読み込まないでください
- 基本的なテストを書く場合、vitest-features.md は読み込まないでください
4. パターンを適用する
各リファレンスファイルには、次の内容が含まれています。
- ❌ アンチパターンを示す誤った例
- ✅ 最適な実装を示す正しい例
- パターンが重要な理由の説明
簡単な例
このスキルが不明瞭なテストを明確で保守しやすいテストに変える方法を示す、変更前/変更後の完全な例については、quick-start.md を参照してください。
コメント
Markdown 対応