test-driven-development
Write the test before the code. The test defines the behavior; the code satisfies it. This is the default for new business logic and bug fixes with a reproducible failure.
Distinct from writing tests for code that already exists — here the test comes first and drives what the code becomes. Distinct from identify-bug (which documents a bug for someone else) — here you write a failing test and fix it.
When to use
- New service/controller method with non-trivial logic.
- A bug fix where you can express the failure as a test (a "red" test that reproduces it).
- Any pure function or deterministic transformation.
Skip for: pure wiring (module registration, DI), config, or a one-line typo fix where a test adds no signal.
The Iron Law
Do not write production code before there is a failing test for it. If code already exists without a test and you are changing its behavior, write the characterization test first (capture current behavior), then the failing test for the new behavior.
If you catch yourself writing implementation before a red test — stop, delete it, and write the test.
The loop
1. RED — write a failing test
Write one test that asserts the required behavior. Follow the repo conventions:
- Test file lives under
tests/mirroring the source path. describe(FunctionOrClass.name)— reference the symbol, not a string literal.- Use the NestJS auto-mocker for dependencies.
- Arrange → Act → Assert; one behavior per test.
Run it. Confirm it fails for the right reason (assertion failure, not a compile/import error). A test that fails because the method doesn't exist yet is fine; a test that fails because of a typo in the test is not.
2. GREEN — minimum code to pass
Write the least code that makes the test pass. Do not add fields, branches, or generality the test does not demand. Resist "I'll need this later" — that is a future test's job.
Run the test. Confirm green.
3. REFACTOR — clean with the test as safety net
Now improve structure: extract helpers, remove duplication, apply the repo's error-logging and naming conventions. Re-run the test after each change; it must stay green.
4. Repeat
Next behavior → next failing test. Grow the implementation one red-green-refactor cycle at a time.
Bug-fix variant
- Write a test that reproduces the bug (RED — it fails, proving the bug).
- Fix the code (GREEN).
- The test now guards against regression permanently.
Never fix a reproducible bug without first capturing it as a failing test.
Red-flags table
| Rationalization | The discipline |
|---|---|
| "I'll write the test after, once the code works" | Test first. Post-hoc tests assert what the code does, not what it should do. |
| "This is too simple to test" | If it has logic, it has a test. If it's pure wiring, skip TDD entirely — don't fake a test. |
| "The test fails because the method doesn't exist — close enough" | Confirm it fails on the assertion, once the method exists as a stub. |
| "Let me add a few extra fields while I'm here" | Only what the current red test demands. Extra code = untested code. |
| "I'll refactor later" | Refactor is step 3, now, while the test is green and the context is fresh. |
Handoff
For test strategy (what to test at unit vs integration vs e2e level), defer to the qa-engineer agent. This skill is the per-change loop; qa-engineer owns the pyramid.