writing-tests
Writing Tests
The rule is no earned signal, no test. Do not add tests to mirror a diff, preserve a literal, or make a tiny change look safer. A test earns its place when it proves behavior that can regress independently from the edit.
Tests are for business behavior, invariants, transformations, runtime and permission boundaries, generation contracts, and user-visible workflows. A test whose only meaningful failure is "this static value changed" is usually duplication, not protection.
Is This Test Worth Writing?
A test earns its place only if it passes all of these. If it fails one, rewrite it or do not write it.
- Behavior, not implementation. It asserts an observable contract: return value, thrown error, persisted-state change, rendered/exported output, API/CLI response, generated artifact, or externally visible side effect. A harmless internal refactor should not break it.
- Mutation-sensitive. At least one assertion fails if behavior is subtly wrong: a condition is negated, a boundary changes, a constant is returned, or persisted state is missing.
- Non-trivial. It is not a getter returning what was set, a pass-through helper returning its input, a wrapper calling the function it wraps, an enum array equalling a hand-copied literal of itself, a public-export assertion, or a runtime assertion of the type system.
- Independent. It passes in isolation and in any order. No shared mutable state, hidden clock coupling, or fixture order dependency.
- Right layer. Pure calculation/parser/reducer/selector logic belongs in a fast unit test. Database, schema, validator, permission, runtime, and multi-unit wiring behavior belongs in an integration test using the project's real harness. Full user journeys belong in end-to-end verification.
- Not duplicated. The same behavior is not already covered at a cheaper layer.
For bug fixes, start from the original failure. The highest-value verification is usually the route, API, browser flow, command, or integration seam that failed. A unit test for a helper invented during the fix does not count as regression coverage unless that helper owns the failing behavior.