testing-contract · git:20261015.8e86e2c · 2026-10-15 · sha256 6e11c68673ddf024

testing-contract git:20261015.8e86e2cC

Immutable. This exact content never changes and is served at /api/v1/blob/6e11c68673ddf024.

+++
name = "testing-contract"
description = "Use before writing or changing a test — whether the change earns one, which existing test absorbs it, and how to write it so it locks real behavior and never flakes."
+++

# Testing Contract

Whether a test exists and which ones the suite holds is decided in the global contract's **Testing**
section. This skill is how to write them. The runner, assertion style, and file layout come from the
repo's own `AGENTS.md` and its existing tests — match what is there.

## Method

1. **Does the change earn a test?** No observable behavior that can break silently → no test, and move
   on. Nothing to state, nothing to justify.
2. **Find the test that already covers the behavior.** Modify it to catch the new case. While in that
   file, delete what is neither a happy path nor a real regression.
3. **Only if nothing can absorb the case, add one** — at the paths the repo's tests already use, with a
   name that states the behavior.
4. **See it red for the right reason** before writing the code.
5. Implement until green.

## A test executes the behavior

- **Assert the observable outcome, never how it was produced.** Internals change in a refactor; the
  contract does not.
- **Reading the source is not a test.** Matching a guard's name, a SQL fragment, or a string in a file
  survives any mutation that keeps the word. Call the code — a fake client, an extracted pure function.
  When only the text exists (a migration with no database in CI), match the whole predicate and count
  its occurrences.
- **A fixture comes from the real constructor, never an object literal.** A literal freezes a state the
  application may no longer produce — a field a form stopped writing keeps a rule satisfied in the test
  while every real entity is blocked.
- **A loop asserts its length first.** A `for` over zero items passes without testing anything.
- **Never assert on a mock.** If the fake satisfies the assertion with the real thing broken, it tests
  the setup.
- **A regression test is proven by the bug.** Reintroduce the historical form of the bug and watch the
  test die. A regression test never seen red locks nothing. Revert the mutant with an edit, never with
  `git checkout`, which also discards uncommitted work.

## Determinism — a flaky test is worse than no test

- **Time is an input.** Clock, time zone, and "now" are injected, never read inside the test.
- **Randomness is an input.** Seed it or inject it.
- **Order is not a fixture.** Each test sets up what it needs and leaves nothing behind.
- **No sleeping to synchronize.** Wait on the condition, not on a duration.
- **Fake what you do not own; use the real thing where it is the point.** The clock, an outbound call,
  a payment gateway are faked at the port. The database in a query test is the thing under test.

A flaky test is quarantined with a deadline or deleted, never re-run until green.