| name | test-driven-development |
| description | Enforce RED-GREEN-REFACTOR cycle for reliable, maintainable code Use when this capability is needed. |
| metadata | {"author":"frankxai"} |
Test-Driven Development (TDD)
The Fundamental Rule
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST.
This is non-negotiable. Code written before tests must be deleted and rewritten with tests first.
Why TDD Matters
Testing after implementation provides false confidence:
- Tests pass immediately (proves nothing)
- No verification that tests catch regressions
- Untested edge cases slip through
TDD ensures every line of production code is proven necessary by a failing test.
The RED-GREEN-REFACTOR Cycle
1. RED - Write a Failing Test
describe('calculateTotal', () => {
it('should sum items with tax', () => {
const items = [{ price: 100 }, { price: 50 }];
const result = calculateTotal(items, 0.08);
expect(result).toBe(162);
});
});
Run the test. It MUST fail. If it passes, you're testing existing code (wrong).
2. Verify RED
Confirm failure is for the right reason:
- ✅ "calculateTotal is not defined" - Correct, function doesn't exist
- ❌ "SyntaxError in test file" - Fix test, not production code
3. GREEN - Minimal Implementation
Write the simplest code that makes the test pass:
function calculateTotal(items: Item[], taxRate: number): number {
const subtotal = items.reduce((sum, item) => sum + item.price, 0);
return subtotal + (subtotal * taxRate);
}
4. Verify GREEN
- All tests pass
- No regressions introduced
- Coverage increased
5. REFACTOR
Improve code quality while keeping tests green:
- Extract functions
- Rename for clarity
- Remove duplication
- Improve performance
Common Rationalizations (Reject All)
| Excuse | Reality |
|---|
| "I'll test after" | Tests passing immediately prove nothing |
| "Already manually tested" | Manual testing isn't systematic or repeatable |
| "Deleting work is wasteful" | Sunk cost fallacy; unverified code is debt |
| "Too simple to test" | Simple code breaks too; testing takes seconds |
| "Keep existing as reference" | You'll adapt it; that's testing after |
Red Flags - Stop Immediately
- Writing code before tests
- Tests passing on first run
- Any "just this once" rationalization
- Skipping the verify steps
FrankX Application
For Next.js Components
describe('EmailSignup', () => {
it('should show error for invalid email', async () => {
render(<EmailSignup />);
await userEvent.type(screen.getByRole('textbox'), 'invalid');
await userEvent.click(screen.getByRole('button'));
expect(screen.getByText(/invalid email/i)).toBeInTheDocument();
});
});
For API Routes
describe('POST /api/subscribe', () => {
it('should return 400 for invalid email', async () => {
const response = await POST({ json: () => ({ email: 'bad' }) });
expect(response.status).toBe(400);
});
});
Test File Organization
project/
├── app/
│ └── api/
│ └── subscribe/
│ └── route.ts
├── tests/
│ ├── unit/ # Fast, isolated tests
│ │ └── api/
│ │ └── subscribe.test.ts
│ ├── integration/ # Cross-component tests
│ └── e2e/ # Playwright tests
│ └── subscribe.spec.ts
Commands
npm test -- --watch
npm test -- api/subscribe.test.ts
npm test -- --coverage
When to Use This Skill
- Writing any new feature
- Fixing bugs (write failing test FIRST)
- Refactoring (ensure tests exist)
- Code reviews (verify TDD was followed)
Related Skills
systematic-debugging - When tests reveal bugs
webapp-testing - For Playwright e2e tests
implementation-planning - Plans include TDD steps
Converted and distributed by TomeVault — claim your Tome and manage your conversions.