Skip to main content

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

  1. Write a test that reproduces the bug (RED — it fails, proving the bug).
  2. Fix the code (GREEN).
  3. The test now guards against regression permanently.

Never fix a reproducible bug without first capturing it as a failing test.

Red-flags table

RationalizationThe 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.