jpskill.com
🛠️ 開発・MCP コミュニティ

testing-unit-integration

ユニットテスト、結合テスト、APIテストなど、様々なテストを綺麗、シンプル、効果的に書くための専門的なアドバイスを提供し、既存テストの改善や新規テスト作成、リファクタリングを支援するSkill。

📜 元の英語説明(参考)

Expert guidance for writing clean, simple, and effective unit, integration, component, microservice, and API tests. Use this skill when reviewing existing tests for violations, writing new tests, or refactoring tests. NOT for end-to-end tests that span multiple processes - use testing-e2e skill instead. Covers AAA pattern, data factories, mocking strategies, DOM testing, database testing, and assertion best practices.

🇯🇵 日本人クリエイター向け解説

一言でいうと

ユニットテスト、結合テスト、APIテストなど、様々なテストを綺麗、シンプル、効果的に書くための専門的なアドバイスを提供し、既存テストの改善や新規テスト作成、リファクタリングを支援するSkill。

※ jpskill.com 編集部が日本のビジネス現場向けに補足した解説です。Skill本体の挙動とは独立した参考情報です。

⚡ おすすめ: コマンド1行でインストール(60秒)

下記のコマンドをコピーしてターミナル(Mac/Linux)または PowerShell(Windows)に貼り付けてください。 ダウンロード → 解凍 → 配置まで全自動。

🍎 Mac / 🐧 Linux
mkdir -p ~/.claude/skills && cd ~/.claude/skills && curl -L -o testing-unit-integration.zip https://jpskill.com/download/10059.zip && unzip -o testing-unit-integration.zip && rm testing-unit-integration.zip
🪟 Windows (PowerShell)
$d = "$env:USERPROFILE\.claude\skills"; ni -Force -ItemType Directory $d | Out-Null; iwr https://jpskill.com/download/10059.zip -OutFile "$d\testing-unit-integration.zip"; Expand-Archive "$d\testing-unit-integration.zip" -DestinationPath $d -Force; ri "$d\testing-unit-integration.zip"

完了後、Claude Code を再起動 → 普通に「動画プロンプト作って」のように話しかけるだけで自動発動します。

💾 手動でダウンロードしたい(コマンドが難しい人向け)
  1. 1. 下の青いボタンを押して testing-unit-integration.zip をダウンロード
  2. 2. ZIPファイルをダブルクリックで解凍 → testing-unit-integration フォルダができる
  3. 3. そのフォルダを C:\Users\あなたの名前\.claude\skills\(Win)または ~/.claude/skills/(Mac)へ移動
  4. 4. Claude Code を再起動

⚠️ ダウンロード・利用は自己責任でお願いします。当サイトは内容・動作・安全性について責任を負いません。

🎯 このSkillでできること

下記の説明文を読むと、このSkillがあなたに何をしてくれるかが分かります。Claudeにこの分野の依頼をすると、自動で発動します。

📦 インストール方法 (3ステップ)

  1. 1. 上の「ダウンロード」ボタンを押して .skill ファイルを取得
  2. 2. ファイル名の拡張子を .skill から .zip に変えて展開(macは自動展開可)
  3. 3. 展開してできたフォルダを、ホームフォルダの .claude/skills/ に置く
    • · macOS / Linux: ~/.claude/skills/
    • · Windows: %USERPROFILE%\.claude\skills\

Claude Code を再起動すれば完了。「このSkillを使って…」と話しかけなくても、関連する依頼で自動的に呼び出されます。

詳しい使い方ガイドを見る →
最終更新
2026-05-18
取得日時
2026-05-18
同梱ファイル
1

📖 Skill本文(日本語訳)

※ 原文(英語/中国語)を Gemini で日本語化したものです。Claude 自身は原文を読みます。誤訳がある場合は原文をご確認ください。

ユニット & 統合テストのベストプラクティス

テストをシンプル、クリーン、一貫性があり、短く保つための専門家によるガイダンスです。テストをレビューする際は、違反したルール番号を報告してください。

対象範囲: ユニットテスト、統合テスト、コンポーネントテスト、マイクロサービス、APIテスト。複数のプロセスにまたがるe2eテストは対象外です - 代わりに testing-e2e スキルを使用してください。

6つの重要なルール

これらは絶対に重要です - これらに従えない場合はコーディングを中止してください。

  1. 最大10ステートメント - テストごとに10個以下のステートメント/式
  2. 必要不可欠な詳細のみ - テスト結果に直接影響する詳細のみを含める
  3. フラットな構造 - if/else、ループ、try-catch、console.log は使用しない
  4. すべてのレイヤーをカバー - 内部のものをモックせず、外部システム呼び出しのみをモックする
  5. スモーキングガン原則 - アサーションのデータは、必ず arrange フェーズに最初に現れるようにする
  6. 自己完結型 - 各テストは独自の状態を作成し、他のテストに依存しない

主要な原則

スモーキングガン原則

アサーションの各データポイントは、必ず arrange フェーズに現れるようにします - 原因と結果を明確に示します。

// Arrange
const activeOrder = buildOrder({ status: 'active' })

// Assert - arrange されたデータを直接参照する
expect(result.id).toBe(activeOrder.id)  // ✅ 明確なつながり
expect(result.id).toBe('123')           // ❌ マジックバリュー

パンくずリスト原則

テストに直接影響するものはすべてテスト内に存在する必要があります。暗黙的な影響は beforeEach に記述し、外部ファイルには記述しないでください。

エクストラマイル原則

必要以上に少し多くをカバーします。保存のテストをしますか?2つのアイテムを使用します。フィルターのテストをしますか?表示されないはずのアイテムも検証します。

デリベレイトファイア原則

失敗しやすいオプションを選択します。ユーザーロールを選択しますか?最も権限の少ないものを使用します。

セクション A - テスト構造

  • A.1 タイトルのパターン: When {scenario}, then {expectation}
  • A.3 最大10ステートメント (複数行の式は1つとしてカウント)
  • A.4 アサーションで arrange されたデータを直接参照する - 値を複製しない ( '123' ではなく activeOrder.id を使用)
  • A.5 3つのフェーズが必要: Arrange, Act, Assert (間に改行を入れる)
  • A.10 テストごとに最大3つのアサーション
  • A.13 完全にフラット - try-catch、ループ、コメント、console.log は使用しない
  • A.18 すべての変数を型付けする - any は使用しない。無効な入力には obj as unknown as Type を使用する
  • A.23 アサーションはテスト内のみで行い、ヘルパーやフック内では行わない
  • A.25 アサーションは Assert フェーズのみで行い、最初や途中では行わない
  • A.28 3行以上のセットアップは /test/helpers フォルダに抽出する

セクション B - テストロジック

  • B.3 スモーキングガン: アサーションデータは arrange に現れる必要がある
  • B.5 テスト結果に直接関係のない詳細は除外する
  • B.10 冗長なアサーションは行わない
  • B.15 巨大なデータセットを比較しない - 特定のトピックに焦点を当てる
  • B.20 テストがデータの存在を前提とする場合は、Arrange で作成する
  • B.23 実装の詳細をテストしない - ユーザー向けの動作のみをテストする
  • B.25 時間ベースの待機 (setTimeout, waitForTimeout) は行わない
  • B.28 beforeEach でクリーンアップする: モック、環境変数、localStorage、グローバル変数
  • B.30 バグを修正する場合: バグのあるコードで失敗するはずだった既存のテストを修正し、新しいテストを追加するだけではいけない
  • B.32 TDD Red フェーズ: 修正を適用する前に、修正されたテストがバグのあるコードで失敗することを確認する

セクション C - テストデータ

  • C.3 データフォルダ内のファクトリファイルからのデータ (buildOrder, buildUser)
  • C.4 ファクトリはデフォルト値を返すが、フィールドのオーバーライドを許可する
  • C.5 ユニバーサルデータ (日付、住所、非ドメイン) には faker を使用する
  • C.7 ファクトリパラメータは型付けする必要がある (テスト対象のコードと同じ型)
  • C.10 ダミー値ではなく、意味のあるドメインデータを使用する
  • C.15 複数オプションのフィールドはデフォルトでランダム化する
  • C.20 配列: デフォルトは2つのアイテム (0、1、または20ではない)

データファクトリの例

import { faker } from "@faker-js/faker";
import { Order } from "../types";

export function buildOrder(overrides: Partial<Order> = {}): Order {
  return {
    id: faker.string.uuid(),
    customerName: faker.person.fullName(),
    status: faker.helpers.arrayElement(["active", "completed", "cancelled"]),
    items: [buildOrderItem(), buildOrderItem()], // デフォルトは2アイテム
    ...overrides,
  };
}

セクション D - アサーション

  • D.7 カスタムコーディング/ループは行わない - ビルトインの expect API を使用する
  • D.11 失敗を検出するための最小限のアサーション - 冗長なチェックは避ける
  • D.13 失敗時に完全な差分を表示するマッチャーを使用する
  • D.15 3つ以上のフィールドを持つオブジェクト: ファクトリを使用し、最大3つのキー値をオーバーライドする

強力なアサーション

// ❌ 弱い - 複数の冗長なアサーション
expect(response).not.toBeNull()
expect(Array.isArray(response)).toBe(true)
expect(response.length).toBe(2)
expect(response[0].id).toBe('123')

// ✅ 強力 - 単一のアサーションですべての問題をキャッチする
expect(response).toEqual([{id: '123'}, {id: '456'}])

セクション E - モック

  • E.1 外部コラボレーター (メール、支払い、外部API) のみをモックする
  • E.3 モックされたコードの型/インターフェースを使用する - コントラクトが変更されたときにコンパイルが失敗する
  • E.5 テストファイル (Arrange または beforeEach) でモックを定義し、外部ファイルでは定義しない
  • E.7 beforeEach ですべてのモックをリセットする
  • E.9 HTTP の関数モックよりもネットワーク傍受 (MSW, Nock) を優先する
  • E.11 統合テスト: 実際のルーター/ナビゲーションを使用し、外部APIのみをモックする
  • E.13 統合テストでは、内部システム (ルーティング、状態管理) をモックしない
  • E.15 統合テストは、ユニットテストと同じ厳密さが必要です - 同じパターン、同じカバレッジ

クラウド/外部 SDK のモック: AWS SDK パターンの場合は references/aws-sdk-mocking.md を参照してください (他のクラウド SDK にも適用可能)。

セクション F - DOM テスト

React Testing Library、Playwright コンポーネントテスト、Storybook の場合:

  • F.1 ユーザー向けのロケーターのみ: getByRole, getByLabel, getByText。test-ids、CSS、xpath は使用しない
  • F.3 位置セレクターは使用しない: nth(i), first(), last()
  • F.5 フレームワークのアサーションメカニズムを使用する (Playwright の場合は自動再試行可能)
  • F.9 waitForSelector は使用しない - 自動再試行可能なアサーションが待機を処理する
  • F.14 外部システムのアサートは行わない - ナビゲーションが発生したことをアサートする
  • F.16 ユーザーに表示される状態をテストする
📜 原文 SKILL.md(Claudeが読む英語/中国語)を展開

Unit & Integration Testing Best Practices

Expert guidance for keeping tests simple, clean, consistent, and short. When reviewing tests, report violated rule numbers.

Scope: Unit, integration, component, microservice, API tests. NOT for e2e tests spanning multiple processes - use testing-e2e skill instead.

The 6 Critical Rules

These are absolutely critical - stop coding if you can't follow them:

  1. Max 10 statements - No more than 10 statements/expressions per test
  2. Essential details only - Include only details that directly affect the test result
  3. Flat structure - No if/else, no loops, no try-catch, no console.log
  4. Cover all layers - Never mock INTERNAL parts, only external system calls
  5. Smoking gun principle - Data in assertion must appear first in arrange phase
  6. Self-contained - Each test creates its own state, never relies on other tests

Key Principles

Smoking Gun Principle

Each data point in assertion must appear in arrange phase - shows cause and effect clearly.

// Arrange
const activeOrder = buildOrder({ status: 'active' })

// Assert - references arranged data directly
expect(result.id).toBe(activeOrder.id)  // ✅ Clear connection
expect(result.id).toBe('123')           // ❌ Magic value

Breadcrumb Principle

Anything affecting test directly should exist in the test. Implicit effects go in beforeEach, never in external files.

Extra Mile Principle

Cover a little more than needed. Testing save? Use two items. Testing filter? Also verify items that should NOT appear.

Deliberate Fire Principle

Choose options more likely to fail. Picking user role? Use least privileged one.

Section A - Test Structure

  • A.1 Title pattern: When {scenario}, then {expectation}
  • A.3 Max 10 statements (multi-line expressions count as one)
  • A.4 Reference arranged data directly in assertions - don't duplicate values (use activeOrder.id not '123')
  • A.5 Three phases required: Arrange, Act, Assert (with line breaks between)
  • A.10 Max 3 assertions per test
  • A.13 Totally flat - no try-catch, loops, comments, console.log
  • A.18 All variables typed - no any. Use obj as unknown as Type for invalid inputs
  • A.23 Assertions only inside test, never in helpers or hooks
  • A.25 Assertions only in Assert phase, never at start or middle
  • A.28 Extract 3+ line setups to /test/helpers folder

Section B - Test Logic

  • B.3 Smoking gun: assertion data must appear in arrange
  • B.5 Exclude details not directly related to test result
  • B.10 No redundant assertions
  • B.15 Don't compare huge datasets - focus on specific topic
  • B.20 If test assumes data exists, create it in Arrange
  • B.23 Don't test implementation details - only user-facing behavior
  • B.25 No time-based waiting (setTimeout, waitForTimeout)
  • B.28 Clean up in beforeEach: mocks, env vars, localStorage, globals
  • B.30 When fixing bugs: amend existing tests that SHOULD have caught it, don't just add new tests
  • B.32 TDD Red phase: verify amended tests FAIL with buggy code before applying fix

Section C - Test Data

  • C.3 Data from factory files in data folder (buildOrder, buildUser)
  • C.4 Factories return defaults but allow field overrides
  • C.5 Use faker for universal data (dates, addresses, non-domain)
  • C.7 Factory params must be typed (same types as code under test)
  • C.10 Use meaningful domain data, not dummy values
  • C.15 Randomize multi-option fields by default
  • C.20 Arrays: default to 2 items (not 0, 1, or 20)

Data Factory Example

import { faker } from "@faker-js/faker";
import { Order } from "../types";

export function buildOrder(overrides: Partial<Order> = {}): Order {
  return {
    id: faker.string.uuid(),
    customerName: faker.person.fullName(),
    status: faker.helpers.arrayElement(["active", "completed", "cancelled"]),
    items: [buildOrderItem(), buildOrderItem()], // Default 2 items
    ...overrides,
  };
}

Section D - Assertions

  • D.7 No custom coding/loops - use built-in expect APIs
  • D.11 Minimal assertions to catch failures - avoid redundant checks
  • D.13 Use matchers that show full diff on failure
  • D.15 Objects with 3+ fields: use factory, override 3 key values max

Strong Assertions

// ❌ WEAK - Multiple redundant assertions
expect(response).not.toBeNull()
expect(Array.isArray(response)).toBe(true)
expect(response.length).toBe(2)
expect(response[0].id).toBe('123')

// ✅ STRONG - Single assertion catches all issues
expect(response).toEqual([{id: '123'}, {id: '456'}])

Section E - Mocking

  • E.1 Mock ONLY external collaborators (email, payment, external APIs)
  • E.3 Use types/interfaces of mocked code - fails compilation when contract changes
  • E.5 Define mocks in test file (Arrange or beforeEach), never external files
  • E.7 Reset all mocks in beforeEach
  • E.9 Prefer network interception (MSW, Nock) over function mocks for HTTP
  • E.11 Integration tests: use REAL router/navigation, only mock external APIs
  • E.13 Never mock internal systems (routing, state management) in integration tests
  • E.15 Integration tests need same rigor as unit tests - same patterns, same coverage

Cloud/External SDK mocking: See references/aws-sdk-mocking.md for AWS SDK patterns (also applicable to other cloud SDKs).

Section F - DOM Testing

For React Testing Library, Playwright component tests, Storybook:

  • F.1 Only user-facing locators: getByRole, getByLabel, getByText. NO test-ids, CSS, xpath
  • F.3 No positional selectors: nth(i), first(), last()
  • F.5 Use framework's assertion mechanism (auto-retriable for Playwright)
  • F.9 No waitForSelector - auto-retriable assertions handle waiting
  • F.14 Don't assert on external systems - assert navigation happened
  • F.16 Test user-VISIBLE state: checkbox checked/unchecked, badge text, button disabled - not just that element exists
// ❌ BAD - Only checks element exists, not its state
expect(screen.getByText('App Name')).toBeInTheDocument();

// ✅ GOOD - Verifies actual user-visible state
expect(screen.getByRole('checkbox', { name: /app name/i })).toBeChecked();
expect(within(row).getByText(/associated/i)).toBeInTheDocument();
expect(screen.getByRole('button', { name: /submit/i })).toBeDisabled();

Section G - Database Testing

  • G.3 Test side effects: add multiple records, assert only intended ones changed
  • G.5 Use type matchers for auto-generated fields: expect.any(Number)
  • G.7 Add randomness to unique fields: ${faker.internet.email()}-${faker.string.nanoid(5)}
  • G.9 Assert via public API, not direct DB queries
  • G.12 Pre-seed only metadata (countries, currencies). Create test-specific records in each test
  • G.14 Each test acts on its own records only - never share test data
  • G.18 Test cascading deletes/updates behavior

Section H - Fake Timers

For testing debounce, throttle, cache TTL, polling, setTimeout/setInterval logic.

Detailed guide: See references/fake-timers.md for patterns and anti-patterns.

Core Rules

  • H.1 Setup/teardown per test: beforeEach(() => vi.useFakeTimers()), afterEach(() => vi.useRealTimers())
  • H.3 Advance timers BEFORE awaiting promises (promise hangs if you await first)
  • H.5 Use runAllTimersAsync() when timer count is unknown
  • H.7 For rejected promises: use rejects matcher OR real timers with short delays
  • H.9 Never use fake timers for simple async/await without timer logic

Quick Pattern

// GOOD: Start promise, advance time, then await
const promise = functionWithTimeout();
await vi.advanceTimersByTimeAsync(1000);
const result = await promise;

// BAD: Await immediately (hangs forever - timers frozen)
const result = await functionWithTimeout(); // ❌ Never completes!

Error Testing Pattern

// For rejected promise tests - use rejects matcher
await expect(handler.execute(mockFn)).rejects.toThrow('fail');

// OR use real timers with short delays
vi.useRealTimers();
const config = { maxRetries: 2, delay: 1 }; // 1ms delay
await expect(retryWithBackoff(mockFn, config)).rejects.toThrow('fail');

Section I - What to Test

  • I.7 Extra mile: testing save? Use two items. Testing filter? Check excluded items too
  • I.10 Deliberate fire: choose options more likely to fail (least privilege role)

Section J - Contract Testing

When testing frontend-backend integration, validate contracts between layers.

  • J.1 Never use as never, as any, as unknown on mock return values - defeats TypeScript safety
  • J.3 Mocks must match ACTUAL API response structure (not idealized/fantasy data)
  • J.5 Destructure responses in tests exactly as consumers will - catches property name mismatches
  • J.7 Response property names must match TypeScript type definitions exactly
  • J.9 Add runtime validation (Zod/io-ts) for API responses - TypeScript can't validate runtime HTTP
// ❌ BAD - Type escape hatch hides contract mismatch
vi.spyOn(api, 'create').mockResolvedValue(mockData as never);

// ❌ BAD - Frontend mock doesn't match actual backend
vi.mocked(api.create).mockResolvedValue({
  application: data,  // Frontend WANTS this
});
// But backend ACTUALLY returns: { data: {...} }

// ✅ GOOD - Mock matches actual backend response
const mockResponse: CreateResponse = {
  data: mockApp,  // Match ACTUAL backend
  created: true,
};
vi.spyOn(api, 'create').mockResolvedValue(mockResponse);

// ✅ GOOD - Destructure as consumers will (catches mismatches)
const { application } = response;  // Will fail if backend uses 'data' not 'application'

Section K - Mock Data Guidelines

Mock data must reflect reality, not fantasy.

  • K.1 Copy real API responses as fixtures - never invent structure from scratch
  • K.3 Document fixture provenance: endpoint, date captured, backend version
  • K.5 Type-check all fixtures against TypeScript interfaces
  • K.7 Include edge cases in fixtures: empty arrays, null values, missing optional fields
/**
 * Real API response from: GET /api/users/123/applications
 * Captured: 2025-12-04
 * Backend version: server@1.2.3
 *
 * Update this fixture if backend changes response structure.
 */
export const REAL_USER_APP_MAPPING: UserAppMapping = {
  _id: '',                    // Mapping ID (often empty)
  applicationId: 'app-123',   // CRITICAL: This is the app ID!
  isActiveForApp: true,
  application: { /* ... */ }
};

// Edge case fixtures
export const EDGE_CASES = {
  emptyList: [],
  nullField: { ...REAL_USER_APP_MAPPING, applicationId: null },
  missingOptional: { _id: '', applicationId: 'app-1' },  // No 'application' field
};

Section L - Boolean Flag Testing

Boolean flags in API responses control critical behavior - test both states.

  • L.1 Test helper defaults can hide bugs - be aware of what defaults to true/false
  • L.3 For every boolean flag in response, test BOTH true and false states
  • L.5 Explicitly set boolean values in test data - never rely on helper defaults
  • L.7 Add @warning JSDoc to helpers with dangerous defaults that could mask bugs
// ❌ BAD - Only tests one state (default true)
const mappings = [
  createMapping({ applicationId: 'app1' })  // isActiveForApp defaults to true
];

// ✅ GOOD - Tests both states explicitly
const mappings = [
  createMapping({ applicationId: 'app1', isActiveForApp: true }),   // Active
  createMapping({ applicationId: 'app2', isActiveForApp: false }),  // Inactive
];

// ✅ GOOD - Document dangerous defaults
/**
 * @warning The default `isActiveForApp` is TRUE. When testing inactive
 * associations, you MUST explicitly set `isActiveForApp: false`.
 * Forgetting this will cause tests to show associations as active
 * when they should be inactive.
 */
export function createMapping(overrides: Partial<Mapping> = {}): Mapping {
  return {
    isActiveForApp: true,  // Dangerous default - document it!
    ...overrides,
  };
}

Section M - Error Handling Testing

Test error scenarios with correct HTTP semantics - 404 is NOT 503.

  • M.1 Map HTTP status to correct error type: 404→NotFoundError, 401→UnauthorizedError, 403→ForbiddenError, 5xx→ServiceUnavailableError
  • M.3 One test per error scenario - don't combine different error types
  • M.5 Validate error messages contain context (resource name, IDs)
  • M.7 Test error propagation through layers (HTTP client → Service → Controller)
  • M.9 Test business requirements, not implementation - ask "what SHOULD happen?"
  • M.11 No generic assertions - rejects.toThrow() needs error type, not just any error

Detailed guide: See references/error-handling-matrix.md for HTTP status mapping and test patterns.

// ❌ BAD - Wrong error mapping (404 is NOT service unavailable)
it('When user not found, then throws ServiceUnavailableError', async () => {
  nock(API_URL).get('/user/123').reply(404);
  await expect(service.getUser('123')).rejects.toThrow(ServiceUnavailableError);
});

// ❌ BAD - Generic assertion (any error passes)
await expect(service.getUser('123')).rejects.toThrow();

// ✅ GOOD - Correct error type for HTTP 404
it('When user not found (404), then throws NotFoundError', async () => {
  nock(API_URL).get('/user/123').reply(404, { message: 'User not found' });

  await expect(service.getUser('123')).rejects.toThrow(NotFoundError);
  await expect(service.getUser('123')).rejects.toThrow(/user.*not found/i);
});

// ✅ GOOD - Test both error type AND message context
it('When unauthorized (401), then throws UnauthorizedError with context', async () => {
  nock(API_URL).get('/user/123').reply(401);

  await expect(service.getUser('123')).rejects.toThrow(UnauthorizedError);
});

Maximum Coverage, Minimal Tests

Achieve comprehensive coverage efficiently:

  • Each test should cover a meaningful scenario, not just a single assertion
  • Combine related assertions (max 3) that test the same behavior
  • Use parameterized tests for similar scenarios with different inputs
  • Focus on behavior, not implementation - fewer tests survive refactoring

BAD Test Example

it('should test orders filtering', async () => { // ❌ A.1 - vague title
  const adminUser = { role: 'admin' } // ❌ I.10 - use least privilege
  const mockOrderService = vi.fn() // ❌ E.1 - mocking internal
  const testData = [{ id: 1, name: 'test1' }] // ❌ C.10 - meaningless data

  render(<OrdersReport data={testData} />)
  const component = screen.getByTestId('orders-report') // ❌ F.1 - test-id

  try { // ❌ A.13 - try-catch not allowed
    await userEvent.click(screen.getByRole('button'))
    let found = [] // ❌ D.7 - custom coding
    for (const row of rows) { found.push(row) } // ❌ A.13 - loop
    expect(found.length).toBe(5) // ❌ B.3 - data not in arrange
    expect(mockOrderService).toHaveBeenCalled() // ❌ B.23 - implementation detail
  } catch (error) {
    console.log('Failed:', error) // ❌ A.13 - console.log
  }
})

GOOD Test Example

beforeEach(() => {
  const currentUser = buildUser({ role: 'viewer' }) // Deliberate fire
  http.get('/api/user/1', () => HttpResponse.json(currentUser))
})

test('When filtering by active status, then only active orders displayed', async () => {
  // Arrange
  const activeOrder = buildOrder({ customerName: faker.person.fullName(), status: 'active' })
  const completedOrder = buildOrder({ customerName: faker.person.fullName(), status: 'completed' })
  http.get('/api/orders', () => HttpResponse.json([activeOrder, completedOrder]))
  const screen = render(<OrdersReport />)

  // Act
  await userEvent.click(screen.getByRole('button', { name: 'Filter by Active' }))

  // Assert
  expect.element(screen.getByRole('cell', { name: activeOrder.customerName })).toBeVisible()
  expect.element(screen.getByRole('cell', { name: completedOrder.customerName })).not.toBeVisible() // Extra mile
})

Rule Violation Reporting

When reviewing tests, report violations as:

Line X: Violates [RULE_NUMBER] - [Brief explanation]

Example:

Line 15: Violates A.13 - Contains try-catch block, tests must be flat
Line 23: Violates B.3 - Assertion uses '123' but this value not in Arrange phase
Line 31: Violates F.1 - Uses getByTestId, should use getByRole or getByLabel