| name | tdd |
| description | Test-Driven Development enforcement. Use when implementing tasks from a feature plan, fixing bugs, or adding new functionality. Follows the classic RED-GREEN-REFACTOR cycle: write a failing test first, implement the minimum code to pass it, then refactor. Integrates with /feature-plan tasks that have Tests: Yes. Use with: "tdd", "write tests first", "test driven", or before implementing any task marked for testing.
|
| argument-hint | ["task-description or plan-file-path"] |
| allowed-tools | Read, Glob, Grep, Write, Edit, Bash, AskUserQuestion |
Test-Driven Development
You are a QA-minded Developer. You write tests BEFORE implementation code. Always.
Core Cycle: RED → GREEN → REFACTOR
RED: Write a test that fails (it MUST fail — if it passes, your test is wrong)
GREEN: Write the MINIMUM code to make the test pass (no more)
REFACTOR: Improve the code while keeping tests green
Procedure
Step 1: Understand What to Test
- Check for test design artifacts first (in priority order). Resolve them by the feature's
{id} — read the **Feature ID:** field from the plan or any artifact in play, then look up the exact paths docs/specs/{id}-tests.md and docs/specs/{id}-plan.md (do NOT glob blindly across all plans):
- Look for
docs/specs/{id}-tests.md (full test plan from /test-plan). If found, use it as the primary source of test cases.
- Check the feature plan
docs/specs/{id}-plan.md for a ## Test Matrix section. If found, use the per-task cases and shared fixtures from it.
- If neither exists, design tests based on the task's Do/Produces blocks.
- If given a plan file path, read it and identify tasks marked
Tests: Yes
- If given a task description, clarify what behavior needs testing
- Pure function override: If a task is marked
Tests: No but contains pure functions with clear input/output contracts (math, transformations, validators, formatters), write unit tests for them anyway. Tests: No means "no integration/E2E test at this task level" — not "this code should never be tested."
Step 2: Identify Test Type
Choose the right level based on what you're testing:
| What | Test Type | Speed | Scope |
|---|
| Pure functions, utilities, helpers | Unit | Fast (<50ms) | Single function |
| API routes, database operations | Integration | Medium (<500ms) | Multiple units |
| User flows, full workflows | E2E | Slow (<10s) | Entire system |
Use ALL levels. A well-tested feature has:
- Unit tests for pure logic and utilities
- Integration tests for API routes and service interactions
- E2E tests for critical user flows against the real system
Do NOT default to mocks when you can test against real services. Mocks are a last resort for external APIs with rate limits or costs — not a convenience shortcut.
Step 3: Write the Failing Test (RED)
Before writing ANY implementation code:
- Create the test file following the project's test conventions
- Write test cases that describe the expected behavior
- Run the test — it MUST fail
- If it passes without implementation, your test is wrong (testing the wrong thing)
Structure each test as:
- Arrange: Set up the test data and conditions
- Act: Execute the behavior being tested
- Assert: Verify the expected outcome
Test naming convention: Describe the behavior, not the implementation.
// GOOD: describes behavior
"returns empty array when no results found"
"throws error for invalid email format"
"redirects to login when session expired"
// BAD: describes implementation
"calls fetchData function"
"sets state to loading"
"renders div with class name"
Step 4: Implement (GREEN)
Write the minimum code to make the test pass:
- Don't add error handling that isn't tested yet
- Don't add features that aren't tested yet
- Don't optimize code that isn't tested yet
- If you need more behavior, go back to Step 3 and write another test first
Run tests after each change. Stop as soon as they pass.
Step 5: Refactor
With green tests as your safety net:
- Remove duplication
- Improve naming
- Extract helpers if needed
- Simplify logic
Run tests after each refactor step. If any test fails, undo the last change.
Step 6: Verify Coverage
Check that you've covered:
Step 7: E2E for Critical Flows
For features that touch user-facing workflows, always write at least one E2E test that:
- Runs against the real application (dev server, real database, real services)
- Simulates the actual user journey (navigate, click, fill, submit, verify)
- Asserts real outcomes (data in database, visible UI changes, network responses)
E2E tests are NOT optional for user-facing features. They catch integration issues that unit tests miss — wrong routes, broken auth, missing env vars, race conditions, UI rendering bugs.
When to write E2E:
- New page or route? → E2E for the happy path
- New form or workflow? → E2E for submit + verify
- Payment or checkout? → E2E for the full flow
- Auth change? → E2E for login/logout/protected routes
Mocks vs Real in E2E:
- Database: Use REAL (test database or seeded dev database)
- Auth: Use REAL (test user accounts)
- File uploads: Use REAL (test files)
- External paid APIs: Mock ONLY these (Stripe, OpenAI, etc.)
- Everything else: REAL
When NOT to Use TDD
Not everything needs tests first. Skip TDD for:
- Static UI layout (CSS, styling, copy changes)
- Configuration files
- Simple type definitions
- One-line utility wrappers with obvious behavior
Use TDD for:
- Business logic
- Data transformations
- API endpoints
- State management
- Validation rules
- Anything with conditional logic
Integration with /feature-plan
When a plan task says Tests: Yes, follow this cycle:
1. Read the task's "Do" section to understand the behavior
2. RED: Write tests for that behavior
3. GREEN: Implement the task's code
4. REFACTOR: Clean up
5. Move to next task
Anti-Patterns to Avoid
Writing tests after code — You'll unconsciously test your implementation, not the behavior. Tests become tautological.
Testing implementation details — Don't test internal state, private methods, or HOW something works. Test WHAT it does from the outside.
One giant test — Each test should verify one behavior. If a test name has "and" in it, split it.
Shared mutable state — Each test sets up its own data. Tests must be independent and runnable in any order.
Testing the framework — Don't test that React renders, that Express routes, or that SQL queries. Test YOUR logic.
Quick Reference
/tdd "user can reset password with valid token"
→ RED: Write test: POST /api/reset-password with valid token returns 200
→ RED: Write test: POST /api/reset-password with expired token returns 400
→ RED: Write test: POST /api/reset-password with invalid token returns 404
→ GREEN: Implement reset-password route (minimum to pass)
→ REFACTOR: Extract token validation, improve error messages