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.