--- name: test-driven-development description: Drives development with tests. Use when implementing any logic, fixing any bug, or changing any behavior. Use when you need to prove that code works, when a bug report arrives, or when you're about to modify existing functionality. --- # Test-Driven Development ## Overview Write a failing test before writing the code that makes it pass. For bug fixes, reproduce the bug with a test before attempting a fix. Tests are proof — "seems right" is not done. A codebase with good tests is an AI agent's superpower; a codebase without tests is a liability. ## When to Use - Implementing any new logic or behavior - Fixing any bug (the Prove-It Pattern) - Modifying existing functionality - Adding edge case handling - Any change that could break existing behavior **When NOT to use:** Pure configuration changes, documentation updates, or static content changes that have no behavioral impact. **Related:** For browser-based changes, combine TDD with runtime verification using Chrome DevTools MCP — see the Browser Testing section below. ## The TDD Cycle ``` RED GREEN REFACTOR Write a test Write minimal code Clean up the that fails ──→ to make it pass ──→ implementation ──→ (repeat) │ │ │ ▼ ▼ ▼ Test FAILS Test PASSES Tests still PASS ``` ### Step 1: RED — Write a Failing Test Write the test first. It must fail. A test that passes immediately proves nothing. ```typescript // RED: This test fails because createTask doesn't exist yet describe("TaskService", () => { it("creates a task with title and default status", async () => { const task = await taskService.createTask({ title: "Buy groceries" }); expect(task.id).toBeDefined(); expect(task.title).toBe("Buy groceries"); expect(task.status).toBe("pending"); expect(task.createdAt).toBeInstanceOf(Date); }); }); ``` ### Step 2: GREEN — Make It Pass Write the minimum code to make the test pass. Don't over-engineer: ```typescript // GREEN: Minimal implementation export async function createTask(input: { title: string }): Promise { const task = { id: generateId(), title: input.title, status: "pending" as const, createdAt: new Date(), }; await db.tasks.insert(task); return task; } ``` ### Step 3: REFACTOR — Clean Up With tests green, improve the code without changing behavior: - Extract shared logic - Improve naming - Remove duplication - Optimize if necessary Run tests after every refactor step to confirm nothing broke. ## The Prove-It Pattern (Bug Fixes) When a bug is reported, **do not start by trying to fix it.** Start by writing a test that reproduces it. ``` Bug report arrives │ ▼ Write a test that demonstrates the bug │ ▼ Test FAILS (confirming the bug exists) │ ▼ Implement the fix │ ▼ Test PASSES (proving the fix works) │ ▼ Run full test suite (no regressions) ``` **Example:** ```typescript // Bug: "Completing a task doesn't update the completedAt timestamp" // Step 1: Write the reproduction test (it should FAIL) it("sets completedAt when task is completed", async () => { const task = await taskService.createTask({ title: "Test" }); const completed = await taskService.completeTask(task.id); expect(completed.status).toBe("completed"); expect(completed.completedAt).toBeInstanceOf(Date); // This fails → bug confirmed }); // Step 2: Fix the bug export async function completeTask(id: string): Promise { return db.tasks.update(id, { status: "completed", completedAt: new Date(), // This was missing }); } // Step 3: Test passes → bug fixed, regression guarded ``` ## Further reading Split out to keep this file under the 250-line cap. - [The test pyramid and writing good tests](references/test-pyramid-and-good-tests.md) - [Anti-patterns, browser testing and verification](references/anti-patterns-and-browser.md)