| name | testing-guide |
| description | Use when writing or reviewing a single good test independent of any framework — structuring it as Arrange-Act-Assert / Four-Phase, meeting FIRST, naming it, choosing and naming the right test double (Dummy / Stub / Spy / Mock / Fake), deciding what to mock and what not to, telling state from behaviour verification, and spotting test smells. Triggers on test double, mock vs stub vs fake vs spy, what should I mock, over-mocking, AAA, FIRST, fragile/flaky/obscure test, Object Mother, Test Data Builder, fixture setup — even when the user doesn't say 'testing'. |
Writing a Single Good Test
How to structure, name, populate, and verify one test — and how to pick the right test double — independent of any framework. It governs the shape of an individual test, not the rhythm in which you write tests.
Essentials
- Structure every test as Arrange-Act-Assert - set up, run one action, verify the outcome, see references/test-structure-and-first.md
- Make each test meet FIRST - Fast, Independent, Repeatable, Self-validating, Timely, see references/test-structure-and-first.md
- Name the right double for the job - Dummy / Stub / Spy / Mock / Fake are distinct, not synonyms, see references/test-double-taxonomy.md
- Prefer state verification; mock only true outputs - assert outcomes, reserve call-checking for external commands, see references/state-vs-behaviour-and-what-to-mock.md
- Mock at architectural seams, not internals - double the payment gateway, never value objects or the system under test, see references/state-vs-behaviour-and-what-to-mock.md
- Read a failing test as a smell report - fragile, obscure, eager, erratic tests each name a fix, see references/test-smells-and-fixtures.md
Gotchas
- "Mock" is not a synonym for "test double". A Mock holds pre-programmed call expectations and is verified; most things people call mocks are actually Stubs that answer queries with canned state.
- A Stub answers queries (state); a Mock verifies commands (behaviour). Asserting on calls when state would do couples the test to the implementation and makes it Fragile.
- AAA is three phases, not four. Explicit Teardown belongs to the Four-Phase Test and is usually unnecessary for a true unit test.
- A Spy is not a Mock: a Spy passively records calls for the test to assert later; a Mock owns the expectations itself and fails on an unexpected or missing call.
- A Fake is not a Stub: a Fake has a real, working, shortcut implementation (an in-memory store); a Stub only returns hard-coded answers with no logic.
- A test that needs many doubles or asserts on internal calls is a coupling signal — fix the production design, and take the coupling diagnosis to connascence-guide, not a bigger fixture.
- FIRST's "I" (Independent / Isolated) and "S" (Self-validating / Self-verifying) both circulate; same intent, neither spelling is wrong.
Example
const auditId = "ignored";
const fares = stubFare({cityCenter: 1250});
const receipts = spyReceipts();
const gateway = mockGateway().expect("charge", 1250, "EUR");
const cards = inMemoryCardStore();
const confirmation = chargeRide(trip, gateway, receipts, fares, cards, auditId);
expect(confirmation.status).toBe("paid");
expect(receipts.sent).toHaveLength(1);
gateway.verify();
Progressive Disclosure