| name | tdd |
| description | Test-driven development โ write the test first, watch it fail, write minimal code to pass |
Test-Driven Development
Write the failing test first. Then make it pass. Then clean up. Every time.
Quick Reference
| Field | Value |
|---|
| Scope | Any code change: new feature, bug fix, refactor |
| Input | Feature request, bug report, or function signature to implement |
| Output | Test file(s) + implementation, all tests green, no dead code |
| Cycle | RED โ GREEN โ REFACTOR (never skip a phase) |
| Core rule | You MUST NOT write production code without a failing test |
| Mocks | Mock only external I/O (network, filesystem, third-party APIs). All internal code uses real implementations. |
The Cycle
RED โ Write a Failing Test
- One behavior per test. If the name needs "and", split it.
- Name describes expected behavior, not implementation.
- Run the test. It MUST fail (not error). Confirm:
- Failure message matches your expectation.
- It fails because the feature is missing, not because of a typo or import error.
- If the test passes immediately, you are testing existing behavior โ rewrite the test.
test("parsePort returns number for valid port string", () => {
expect(parsePort("8080")).toBe(8080);
});
GREEN โ Minimal Code to Pass
- Write the simplest code that makes the failing test pass.
- You MUST NOT add features, refactor, or "improve" beyond what the test demands.
- Run all tests. They MUST all pass with clean output.
- If the new test fails, fix the code โ not the test.
function parsePort(value: string): number {
return Number(value);
}
REFACTOR โ Clean Up (Green Tests Only)
- Remove duplication, improve names, extract helpers.
- Keep all tests green. You MUST NOT add behavior during refactor.
- Limit refactor scope to code touched in this cycle. Time-box: if it takes more than a few minutes, defer to a separate cycle.
function parsePort(v: string): number { return Number(v); }
function parseHost(v: string): string { return v.trim().toLowerCase(); }
function sanitize(v: string): string { return v.trim(); }
function parsePort(v: string): number { return Number(sanitize(v)); }
function parseHost(v: string): string { return sanitize(v).toLowerCase(); }
Bug Fix Flow
Bug reported โ Write failing test that reproduces it โ GREEN โ REFACTOR
test("parsePort trims whitespace", () => {
expect(parsePort(" 8080 ")).toBe(8080);
});
function parsePort(v: string): number {
return Number(v.trim());
}
MUST DO / MUST NOT DO
| MUST DO | MUST NOT DO |
|---|
| Run the test and watch it fail before writing code | Write production code before a failing test exists |
| Confirm failure is for the expected reason | Ignore why a test failed (typo โ missing feature) |
| Write the simplest passing implementation | Add unrequested features during GREEN |
| Keep all tests green during REFACTOR | Add new behavior during REFACTOR |
| Use real implementations for internal code | Mock internal modules to avoid setup effort |
| Write one assertion per behavior | Stuff multiple behaviors into one test |
| Test edge cases and error paths | Test only the happy path |
When Stuck
| Problem | Solution |
|---|
| Don't know how to test it | Write the API you wish existed. Write the assertion first. |
| Test too complicated | Interface too complicated. Simplify the design. |
| Must mock everything | Code too coupled. Introduce dependency injection. |
| Test setup is huge | Extract test helpers. Still complex? Simplify the production design. |
| Can't find test files / runner | Check package.json scripts, look for existing *.test.* or *.spec.* files, match the project's conventions. |
Verification Checklist