> ## Documentation Index
> Fetch the complete documentation index at: https://mintlify.com/obra/superpowers/llms.txt
> Use this file to discover all available pages before exploring further.

# Test-Driven Development (TDD)

> Write failing tests first, implement minimal code to pass, refactor. No exceptions.

<Note>
  **Core principle:** If you didn't watch the test fail, you don't know if it tests the right thing.
</Note>

## The Iron Law

```
NO PRODUCTION CODE WITHOUT A FAILING TEST FIRST
```

Write code before the test? Delete it. Start over.

<Warning>
  Violating the letter of the rules is violating the spirit of the rules. No exceptions.
</Warning>

## When to Use

**Always:**

* New features
* Bug fixes
* Refactoring
* Behavior changes

**Exceptions (ask first):**

* Throwaway prototypes
* Generated code
* Configuration files

Thinking "skip TDD just this once"? Stop. That's rationalization.

## Red-Green-Refactor

TDD follows a strict three-phase cycle:

<Steps>
  <Step title="RED: Write Failing Test">
    Write one minimal test showing what should happen.

    **Good test:**

    ```typescript theme={null}
    test('retries failed operations 3 times', async () => {
      let attempts = 0;
      const operation = () => {
        attempts++;
        if (attempts < 3) throw new Error('fail');
        return 'success';
      };

      const result = await retryOperation(operation);

      expect(result).toBe('success');
      expect(attempts).toBe(3);
    });
    ```

    Clear name, tests real behavior, one thing.

    **Bad test:**

    ```typescript theme={null}
    test('retry works', async () => {
      const mock = jest.fn()
        .mockRejectedValueOnce(new Error())
        .mockRejectedValueOnce(new Error())
        .mockResolvedValueOnce('success');
      await retryOperation(mock);
      expect(mock).toHaveBeenCalledTimes(3);
    });
    ```

    Vague name, tests mock not code.

    **Requirements:**

    * One behavior
    * Clear name
    * Real code (no mocks unless unavoidable)
  </Step>

  <Step title="Verify RED: Watch It Fail">
    **MANDATORY. Never skip.**

    ```bash theme={null}
    npm test path/to/test.test.ts
    ```

    Confirm:

    * Test fails (not errors)
    * Failure message is expected
    * Fails because feature missing (not typos)

    **Test passes?** You're testing existing behavior. Fix test.

    **Test errors?** Fix error, re-run until it fails correctly.
  </Step>

  <Step title="GREEN: Minimal Code">
    Write simplest code to pass the test.

    **Good implementation:**

    ```typescript theme={null}
    async function retryOperation<T>(fn: () => Promise<T>): Promise<T> {
      for (let i = 0; i < 3; i++) {
        try {
          return await fn();
        } catch (e) {
          if (i === 2) throw e;
        }
      }
      throw new Error('unreachable');
    }
    ```

    Just enough to pass.

    **Bad implementation:**

    ```typescript theme={null}
    async function retryOperation<T>(
      fn: () => Promise<T>,
      options?: {
        maxRetries?: number;
        backoff?: 'linear' | 'exponential';
        onRetry?: (attempt: number) => void;
      }
    ): Promise<T> {
      // YAGNI - over-engineered
    }
    ```

    Don't add features, refactor other code, or "improve" beyond the test.
  </Step>

  <Step title="Verify GREEN: Watch It Pass">
    **MANDATORY.**

    ```bash theme={null}
    npm test path/to/test.test.ts
    ```

    Confirm:

    * Test passes
    * Other tests still pass
    * Output pristine (no errors, warnings)

    **Test fails?** Fix code, not test.

    **Other tests fail?** Fix now.
  </Step>

  <Step title="REFACTOR: Clean Up">
    After green only:

    * Remove duplication
    * Improve names
    * Extract helpers

    Keep tests green. Don't add behavior.
  </Step>

  <Step title="Repeat">
    Next failing test for next feature.
  </Step>
</Steps>

## Visual Workflow

```mermaid theme={null}
graph LR
    A[RED: Write failing test] --> B{Verify fails correctly?}
    B -->|Wrong failure| A
    B -->|Yes| C[GREEN: Minimal code]
    C --> D{Verify passes?}
    D -->|No| C
    D -->|All green| E[REFACTOR: Clean up]
    E --> F{Stay green?}
    F -->|Yes| G[Next test]
    F -->|No| E
    G --> A
```

## Why Order Matters

<Accordion title="&#x22;I'll write tests after to verify it works&#x22;">
  Tests written after code pass immediately. Passing immediately proves nothing:

  * Might test wrong thing
  * Might test implementation, not behavior
  * Might miss edge cases you forgot
  * You never saw it catch the bug

  Test-first forces you to see the test fail, proving it actually tests something.
</Accordion>

<Accordion title="&#x22;I already manually tested all the edge cases&#x22;">
  Manual testing is ad-hoc. You think you tested everything but:

  * No record of what you tested
  * Can't re-run when code changes
  * Easy to forget cases under pressure
  * "It worked when I tried it" ≠ comprehensive

  Automated tests are systematic. They run the same way every time.
</Accordion>

<Accordion title="&#x22;Deleting X hours of work is wasteful&#x22;">
  Sunk cost fallacy. The time is already gone. Your choice now:

  * Delete and rewrite with TDD (X more hours, high confidence)
  * Keep it and add tests after (30 min, low confidence, likely bugs)

  The "waste" is keeping code you can't trust. Working code without real tests is technical debt.
</Accordion>

<Accordion title="&#x22;TDD is dogmatic, being pragmatic means adapting&#x22;">
  TDD IS pragmatic:

  * Finds bugs before commit (faster than debugging after)
  * Prevents regressions (tests catch breaks immediately)
  * Documents behavior (tests show how to use code)
  * Enables refactoring (change freely, tests catch breaks)

  "Pragmatic" shortcuts = debugging in production = slower.
</Accordion>

<Accordion title="&#x22;Tests after achieve the same goals - it's spirit not ritual&#x22;">
  No. Tests-after answer "What does this do?" Tests-first answer "What should this do?"

  Tests-after are biased by your implementation. You test what you built, not what's required. You verify remembered edge cases, not discovered ones.

  Tests-first force edge case discovery before implementing. Tests-after verify you remembered everything (you didn't).

  30 minutes of tests after ≠ TDD. You get coverage, lose proof tests work.
</Accordion>

## Good Tests

| Quality          | Good                                | Bad                                                 |
| ---------------- | ----------------------------------- | --------------------------------------------------- |
| **Minimal**      | One thing. "and" in name? Split it. | `test('validates email and domain and whitespace')` |
| **Clear**        | Name describes behavior             | `test('test1')`                                     |
| **Shows intent** | Demonstrates desired API            | Obscures what code should do                        |

## Example: Bug Fix

**Bug:** Empty email accepted

<Steps>
  <Step title="RED">
    ```typescript theme={null}
    test('rejects empty email', async () => {
      const result = await submitForm({ email: '' });
      expect(result.error).toBe('Email required');
    });
    ```
  </Step>

  <Step title="Verify RED">
    ```bash theme={null}
    $ npm test
    FAIL: expected 'Email required', got undefined
    ```
  </Step>

  <Step title="GREEN">
    ```typescript theme={null}
    function submitForm(data: FormData) {
      if (!data.email?.trim()) {
        return { error: 'Email required' };
      }
      // ...
    }
    ```
  </Step>

  <Step title="Verify GREEN">
    ```bash theme={null}
    $ npm test
    PASS
    ```
  </Step>

  <Step title="REFACTOR">
    Extract validation for multiple fields if needed.
  </Step>
</Steps>

## Red Flags - STOP and Start Over

If you:

* Wrote code before test
* Wrote test after implementation
* Test passed immediately
* Can't explain why test failed
* Added tests "later"
* Think "just this once"
* Think "I already manually tested it"
* Think "Tests after achieve the same purpose"
* Think "It's about spirit not ritual"
* Think "Keep as reference" or "adapt existing code"
* Think "Already spent X hours, deleting is wasteful"
* Think "TDD is dogmatic, I'm being pragmatic"
* Think "This is different because..."

**All of these mean: Delete code. Start over with TDD.**

## Common Rationalizations

| Excuse                                 | Reality                                                                 |
| -------------------------------------- | ----------------------------------------------------------------------- |
| "Too simple to test"                   | Simple code breaks. Test takes 30 seconds.                              |
| "I'll test after"                      | Tests passing immediately prove nothing.                                |
| "Tests after achieve same goals"       | Tests-after = "what does this do?" Tests-first = "what should this do?" |
| "Already manually tested"              | Ad-hoc ≠ systematic. No record, can't re-run.                           |
| "Deleting X hours is wasteful"         | Sunk cost fallacy. Keeping unverified code is technical debt.           |
| "Keep as reference, write tests first" | You'll adapt it. That's testing after. Delete means delete.             |
| "Need to explore first"                | Fine. Throw away exploration, start with TDD.                           |
| "Test hard = design unclear"           | Listen to test. Hard to test = hard to use.                             |
| "TDD will slow me down"                | TDD faster than debugging. Pragmatic = test-first.                      |

## When Stuck

| Problem                | Solution                                                             |
| ---------------------- | -------------------------------------------------------------------- |
| Don't know how to test | Write wished-for API. Write assertion first. Ask your human partner. |
| Test too complicated   | Design too complicated. Simplify interface.                          |
| Must mock everything   | Code too coupled. Use dependency injection.                          |
| Test setup huge        | Extract helpers. Still complex? Simplify design.                     |

## Verification Checklist

Before marking work complete:

* [ ] Every new function/method has a test
* [ ] Watched each test fail before implementing
* [ ] Each test failed for expected reason (feature missing, not typo)
* [ ] Wrote minimal code to pass each test
* [ ] All tests pass
* [ ] Output pristine (no errors, warnings)
* [ ] Tests use real code (mocks only if unavoidable)
* [ ] Edge cases and errors covered

Can't check all boxes? You skipped TDD. Start over.

## Related Skills

* **[Systematic Debugging](/skills/systematic-debugging)**: Write failing test to reproduce bug (Phase 4, Step 1)
* **[Executing Plans](/skills/executing-plans)**: Follow plan's TDD steps for each task
* **[Subagent-Driven Development](/skills/subagent-driven-development)**: Subagents use TDD for implementation

<Tip>
  TDD is not about testing. It's about design. Tests written first guide you toward simple, decoupled, testable code. Tests written after just verify whatever you already built.
</Tip>
