typescript-unit-testing について
TypeScriptプロジェクト向けのユニットテストスキルです。セットアップ、作成、レビュー、実行、デバッグ、最適化の作業別ワークフローを選び、依存関係の分離や境界・エラーケースの確認を支援します。
主な機能
- 01作業に応じたテストワークフロー選択
- 02NestJSテストのリファレンス
- 03データベースとメッセージングのテスト
- 04AAAとモックのルール
- 05失敗テストを個別に調査
- 06性能とカバレッジの要件
活用シーン
- 01新規プロジェクトのテスト基盤設定
- 02サービスやコントローラーの単体テスト作成
- 03テスト品質のレビューと境界ケースの補完
- 04失敗テストや遅いテストの原因調査
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ bmad-labs/ skills --skill typescript-unit-testingインストール後、Claude Code で/typescript-unit-testingを入力して読み込まれたか確認します。
同梱ファイル
SKILL.md と一緒にインストールされる参考資料やスクリプト。
LICENSEreferences/common/assertions.mddetect-open-handles.mdexamples.mdknowledge.mdperformance-optimization.mdrules.mdreferences/kafka/kafka.mdreferences/mocking/deep-mocked.mdfactories.mdjest-native.mdreferences/nestjs/controllers.mdguards.mdinterceptors.mdpipes-filters.mdservices.mdreferences/redis/redis.mdreferences/repository/mongodb.mdpostgres.mdworkflows/debugging/workflow.mdworkflows/debugging/steps/step-01-init.mdstep-01b-continue.mdstep-02-classify-failure.mdstep-03-analyze.mdstep-04-implement-fix.mdstep-05-verify-fix.mdstep-06-document-resolution.mdworkflows/debugging/templates/output-template.mdworkflows/optimizing/steps/step-01-init.mdstep-01b-continue.md詳細
ユニットテストスキル
ユニットテストでは、すべての外部依存関係をモックし、個々の関数、メソッド、クラスを分離して検証します。
ワークフロー
ユニットテストのタスクを手順に沿って実行するには、適切なワークフローを使用してください。
| ワークフロー | 目的 | 使用する場面 |
|---|---|---|
| セットアップ | テスト基盤を初期化する | 新規プロジェクト、またはテストのセットアップが不足している場合 |
| 作成 | 新しいユニットテストを作成する | コンポーネントのテストを作成する場合 |
| レビュー | 既存のテストをレビューする | コードレビュー、品質監査 |
| 実行 | テストを実行する | テストの実行、結果の分析 |
| デバッグ | 失敗したテストを修正する | テストが失敗し、原因を特定する必要がある場合 |
| 最適化 | テストのパフォーマンスを改善する | テストが遅い場合、保守性を高める場合 |
ワークフローの選択ガイド
重要:テストタスクを開始する前に、ユーザーの意図を特定し、適切なワークフローを読み込んでください。
ユーザーの意図を検出 → ワークフローを選択
| ユーザーの発言・要望 | 読み込むワークフロー | ファイル |
|---|---|---|
| 「テストをセットアップして」「Jest を設定して」「プロジェクトにテストを追加して」「テスト依存関係をインストールして」 | セットアップ | workflows/setup/workflow.md |
| 「テストを書いて」「テストを追加して」「テストを作成して」「このサービス/コントローラーをテストして」 | 作成 | workflows/writing/workflow.md |
| 「テストをレビューして」「テストの品質を確認して」「テストを監査して」「これらのテストは適切?」 | レビュー | workflows/reviewing/workflow.md |
| 「テストを実行して」「テストを実行して」「テストが成功するか確認して」「テスト結果を表示して」 | 実行 | workflows/running/workflow.md |
| 「テストを修正して」「テストをデバッグして」「テストが失敗する」「このテストが壊れている理由は?」 | デバッグ | workflows/debugging/workflow.md |
| 「テストを高速化して」「テストを最適化して」「テストが遅い」「オープンハンドルを修正して」 | 最適化 | workflows/optimizing/workflow.md |
ワークフロー実行プロトコル
- 必ず最初にワークフローファイルを読み込む — 作業に着手する前に、ワークフロー全体を読んでください
- 各手順を順番に実行する — 次に進む前に、チェックポイントを完了してください
- 指示に従ってナレッジファイルを読み込む — 各ワークフローで、読み込む
references/ファイルが指定されています - 完了後に準拠状況を確認する — 品質を確保するため、関連する参照ファイルを再度読み込んでください
ナレッジベースの構成
references/
├── common/ # Core testing fundamentals
│ ├── knowledge.md # Testing philosophy and test pyramid
│ ├── rules.md # Mandatory testing rules (AAA, naming, coverage)
│ ├── assertions.md # Assertion patterns and matchers
│ ├── examples.md # Comprehensive examples by category
│ ├── detect-open-handles.md # Open handle detection and cleanup
│ └── performance-optimization.md # Jest runtime optimization
│
├── nestjs/ # NestJS component testing
│ ├── services.md # Service/usecase testing patterns
│ ├── controllers.md # Controller testing patterns
│ ├── guards.md # Guard testing patterns
│ ├── interceptors.md # Interceptor testing patterns
│ └── pipes-filters.md # Pipe and filter testing
│
├── mocking/ # Mock patterns and strategies
│ ├── deep-mocked.md # @golevelup/ts-jest patterns
│ ├── jest-native.md # Jest.fn, spyOn, mock patterns
│ └── factories.md # Test data factory patterns
│
├── repository/ # Repository testing
│ ├── mongodb.md # mongodb-memory-server patterns
│ └── postgres.md # pg-mem patterns
│
├── kafka/ # NestJS Kafka microservices testing
│ └── kafka.md # ClientKafka, @MessagePattern, @EventPattern handlers
│
└── redis/ # Redis cache testing
└── redis.md # Cache operations, health checks, graceful degradation
タスク別クイックリファレンス
ユニットテストを作成する
- 必須:
references/common/rules.mdを読む — AAA パターン、命名、カバレッジ references/common/assertions.mdを読む — アサーションのベストプラクティス- コンポーネントに応じたファイルを読む:
- サービス:
references/nestjs/services.md - コントローラー:
references/nestjs/controllers.md - ガード:
references/nestjs/guards.md - インターセプター:
references/nestjs/interceptors.md - パイプ/フィルター:
references/nestjs/pipes-filters.md
- サービス:
モックをセットアップする
references/mocking/deep-mocked.mdを読む — DeepMocked のパターンreferences/mocking/jest-native.mdを読む — Jest 標準のパターンreferences/mocking/factories.mdを読む — テストデータファクトリー
リポジトリーをテストする
- MongoDB:
references/repository/mongodb.md - PostgreSQL:
references/repository/postgres.md
Kafka をテストする(NestJS マイクロサービス)
references/kafka/kafka.mdを読む — ClientKafka のモック、@MessagePattern/@EventPattern ハンドラー、emit/send のテスト
Redis をテストする
references/redis/redis.mdを読む — キャッシュ操作、ヘルスチェック、適切な縮退動作
例
- 包括的なパターンについては
references/common/examples.mdを読む
テストのパフォーマンスを最適化する
references/common/performance-optimization.mdを読む — ワーカー設定、キャッシュ、CI の最適化references/common/detect-open-handles.mdを読む — 正常終了を妨げるオープンハンドルの修正
オープンハンドルをデバッグする
references/common/detect-open-handles.mdを読む — 検出コマンド、一般的なハンドルの種類、クリーンアップのパターン
基本原則
0. コンテキスト効率(テスト出力を一時ファイルに保存)
ユニットテストの出力は、必ずコンソールではなく一時ファイルにリダイレクトしてください。テスト出力は冗長になりやすく、エージェントのコンテキストを圧迫します。
重要:複数のエージェントを実行したときに競合しないよう、ファイル名には一意のセッション ID を使用してください。
# Initialize session (once at start of testing session)
export UT_SESSION=$(date +%s)-$$
# Standard pattern - redirect output to temp file (NO console output)
npm test > /tmp/ut-${UT_SESSION}-output.log 2>&1
# Read summary only (last 50 lines)
tail -50 /tmp/ut-${UT_SESSION}-output.log
# Get failure details
grep -B 2 -A 15 "FAIL\|✕" /tmp/ut-${UT_SESSION}-output.log
# Cleanup when done
rm -f /tmp/ut-${UT_SESSION}-*.log /tmp/ut-${UT_SESSION}-*.md
一時ファイル(エージェントごとに一意な ${UT_SESSION} を使用):
/tmp/ut-${UT_SESSION}-output.log- テスト出力全体/tmp/ut-${UT_SESSION}-failures.md- 1件ずつ修正するための追跡ファイル/tmp/ut-${UT_SESSION}-debug.log- デバッグ実行/tmp/ut-${UT_SESSION}-verify.log- 検証実行/tmp/ut-${UT_SESSION}-coverage.log- カバレッジ出力
1. AAA パターン(必須)
すべてのユニットテストは、必ず Arrange-Act-Assert に従ってください:
it('should return user when found', async () => {
// Arrange
const userId = 'user-123';
mockRepository.findById.mockResolvedValue({
id: userId,
email: '[email protected]',
name: 'Test User',
});
// Act
const result = await target.getUser(userId);
// Assert
expect(result).toEqual({
id: userId,
email: '[email protected]',
name: 'Test User',
});
expect(mockRepository.findById).toHaveBeenCalledWith(userId);
});
2. SUT には target を使用
テスト対象のシステムには、常に target という名前を使用してください:
let target: UserService;
let mockRepository: DeepMocked<UserRepository>;
3. DeepMocked パターン
型安全なモックには @golevelup/ts-jest を使用してください:
import { createMock, DeepMocked } from '@golevelup/ts-jest';
let mockService: DeepMocked<UserService>;
beforeEach(() => {
mockService = createMock<UserService>();
});
4. 具体的なアサーション
存在することだけでなく、正確な値をアサートしてください:
// WRONG
expect(result).toBeDefined();
expect(result.id).toBeDefined();
// CORRECT
expect(result).toEqual({
id: 'user-123',
email: '[email protected]',
name: 'Test User',
});
5. すべての依存関係をモック
外部サービスをモックし、ユニットテストでは実際のデータベースを使用しないでください:
// Unit Test: Mock repository
{ provide: UserRepository, useValue: mockRepository }
// Repository Test: Use in-memory database
const mongoServer = await createMongoMemoryServer();
標準テストテンプレート
import { Test, TestingModule } from '@nestjs/testing';
import { createMock, DeepMocked } from '@golevelup/ts-jest';
import { MockLoggerService } from 'src/shared/logger/services/mock-logger.service';
describe('UserService', () => {
let target: UserService;
let mockRepository: DeepMocked<UserRepository>;
beforeEach(async () => {
// Arrange: Create mocks
mockRepository = createMock<UserRepository>();
const module: TestingModule = await Test.createTestingModule({
providers: [
UserService,
{ provide: UserRepository, useValue: mockRepository },
],
})
.setLogger(new MockLoggerService())
.compile();
target = module.get<UserService>(UserService);
});
afterEach(() => {
jest.clearAllMocks();
});
describe('getUser', () => {
it('should return user when found', async () => {
// Arrange
mockRepository.findById.mockResolvedValue({
id: 'user-123',
email: '[email protected]',
});
// Act
const result = await target.getUser('user-123');
// Assert
expect(result).toEqual({ id: 'user-123', email: '[email protected]' });
});
it('should throw NotFoundException when user not found', async () => {
// Arrange
mockRepository.findById.mockResolvedValue(null);
// Act & Assert
await expect(target.getUser('invalid')).rejects.toThrow(NotFoundException);
});
});
});
テストカバレッジの要件
| カテゴリ | 優先度 | 説明 |
|---|---|---|
| 正常系 | 必須 | 有効な入力に対して期待される出力が得られる |
| エッジケース | 必須 | 空の配列、null 値、境界値 |
| エラーケース | 必須 | 見つからない場合、検証の失敗 |
| 例外動作 | 必須 | 正しい型、エラーコード、メッセージ |
| ビジネスルール | 必須 | ドメインロジック、計算 |
| 入力検証 | 必須 | 無効な入力、型の不一致 |
カバレッジ目標: 新規コードで 80% 以上
失敗解決プロトコル
最重要:テストは1件ずつ修正してください。修正中にテストスイート全体を繰り返し実行しないでください。
ユニットテストが失敗した場合:
- セッションを初期化(開始時に1回):
export UT_SESSION=$(date +%s)-$$ - 追跡ファイルを作成:失敗したテストをすべて
/tmp/ut-${UT_SESSION}-failures.mdに記録します - 失敗したテストを1件選択し、そのテストだけに取り組みます
- そのテストだけを実行します(テストスイート全体は実行しないでください):
npm test -- -t "test name" > /tmp/ut-${UT_SESSION}-debug.log 2>&1 tail -50 /tmp/ut-${UT_SESSION}-debug.log - 問題を修正します。エラーを分析し、対象を絞って修正します
- 修正を検証します。同じテストを3~5回実行します:
for i in {1..5}; do npm test -- -t "test name" > /tmp/ut-${UT_SESSION}-run$i.log 2>&1 && echo "Run $i: PASS" || echo "Run $i: FAIL"; done - 追跡ファイルで FIXED と記録します
- 次の失敗したテストに進み、手順3~7を繰り返します
- すべての個別テストが通過した後に限り、テストスイート全体を1回だけ実行します
- クリーンアップ:
rm -f /tmp/ut-${UT_SESSION}-*.log /tmp/ut-${UT_SESSION}-*.md
理由:テストスイート全体の実行は、時間とコンテキストを浪費します。失敗したテストごとに出力が増え、デバッグが難しくなります。
命名規則
テストファイル
- パターン:
*.spec.ts - 場所:ソースファイルと同じディレクトリ
テスト構造
describe('ClassName', () => {
describe('methodName', () => {
it('should [expected behavior] when [condition]', () => {});
});
});
変数名
| 変数 | 規則 |
|---|---|
| SUT | target |
| モック | mock を接頭辞として付ける(mockRepository、mockService) |
| モック型 | DeepMocked<T> |
ユニットテストの対象外
次の項目に対して、ユニットテストを作成しないでください:
- インターフェース - 型定義のみで、実行時の動作がありません
- 列挙型 - 静的な値のマッピングで、テストするロジックがありません
- 定数 - 静的な値で、動作がありません
- 型エイリアス - 型定義のみです
- 単純な DTO - ロジックを含まないデータ構造です
実行可能なロジックを含むファイル(メソッドを持つクラス、動作を持つ関数)のみをテストしてください。
避けるべきアンチパターン
| しないこと | 理由 | 代わりにすること |
|---|---|---|
| 存在することだけをアサートする | 値が間違っていても検出できません | 具体的な値をアサートする |
| 条件付きアサーション | 非決定的になります | テストケースを分ける |
| プライベートメソッドをテストする | 実装に密結合します | パブリックインターフェース経由でテストする |
| テスト間で状態を共有する | 不安定なテストの原因になります | beforeEach で毎回新しくセットアップする |
| サービス内でリポジトリをモックする | 実装をテストすることになります | インターフェースをモックする |
| モックの検証を省略する | 動作を検証できません | モックの呼び出しを検証する |
| インターフェース、列挙型、定数をテストする | テストする動作がありません | これらのファイルはスキップする |
チェックリスト
セットアップ:
- テスト対象のシステムには
targetを使用する - すべてのモックに
mockプレフィックスを使用する -
DeepMocked<T>型を使用する -
.setLogger(new MockLoggerService())を含める - コメント付きの AAA パターンに従う
-
afterEachでモックをリセットする
カバレッジ:
- すべてのパブリックメソッドについて正常系のテストを行う
- エッジケース(空、null、境界値)のテストを行う
- エラーケース(見つからない場合、検証の失敗)のテストを行う
- 例外の型とエラーコードを検証する
- モックの呼び出し(パラメーターと回数)を検証する
品質:
- 新しいコードのカバレッジを80%以上にする
- ログ呼び出しに対するアサーションを行わない
- テスト間に依存関係を持たせない
- いずれかのフィールドが異なる場合にテストが失敗する