|
| 1 | +# ADR-0008: How we write unit tests |
| 2 | + |
| 3 | +**Date**: 2026-07-13 |
| 4 | +**Status**: accepted |
| 5 | +**Deciders**: Yeganathan S |
| 6 | + |
| 7 | +## Context |
| 8 | + |
| 9 | +Unit tests should stay easy to read and cheap to change. Without a shared style, |
| 10 | +suites either bury the case in setup and weak assertions, or grow a private |
| 11 | +layer of helpers that obscures what is under test. |
| 12 | + |
| 13 | +We need a small set of principles for designing unit tests: what a case is, |
| 14 | +what it asserts, and what may be shared. |
| 15 | + |
| 16 | +## Decision |
| 17 | + |
| 18 | +Treat each unit test as a **named scenario**. Compose data at the call site, |
| 19 | +assert the outcome that proves the behavior, and extract helpers only when |
| 20 | +reuse is real. |
| 21 | + |
| 22 | +### Scenarios |
| 23 | + |
| 24 | +- Name the real situation under test — one decision or path per case. |
| 25 | +- Setup should make that story obvious (concrete labels, dates, relationships). |
| 26 | +- Do not combine unrelated behaviors in a single test. |
| 27 | +- Prefer fewer tests that fail clearly over many that pass for the wrong reason. |
| 28 | + |
| 29 | +### Assertions |
| 30 | + |
| 31 | +- Assert the result the unit owns (shape, exact values, exact counts). |
| 32 | +- Prefer exact expectations when the outcome is deterministic. |
| 33 | +- Avoid loose probes (`some`, `greaterThan`, optional finds) unless the claim |
| 34 | + truly is approximate. |
| 35 | + |
| 36 | +### What to share |
| 37 | + |
| 38 | +| Layer | Where | Belongs | |
| 39 | +| --- | --- | --- | |
| 40 | +| Scenario | Inside the test | Data that defines *this* case | |
| 41 | +| File-local | Top of the suite file | Constants or tiny helpers used more than once in this file | |
| 42 | +| Shared kit | `@crowd/test-kit` | Helpers needed by a second suite | |
| 43 | + |
| 44 | +**Rules of thumb** |
| 45 | + |
| 46 | +- Start inline. Extract to file-local only after real repetition. |
| 47 | +- Promote to the shared kit only when another suite needs the same helper — |
| 48 | + and keep it sharp (create / fill), not a full scenario builder. |
| 49 | +- Shared fixtures at the top of a file should be stable shapes, not multipurpose |
| 50 | + objects that different tests reinterpret by index. |
| 51 | +- A few duplicated lines that tell the story beat a shared fixture that hides it. |
| 52 | +- Reuse production types and enums. Do not invent test-only mirrors. |
| 53 | +- Name helpers after what they do, not after a vague dump of domain data. |
| 54 | +- A short list of clear file-local helpers is fine; wrappers that only pass data |
| 55 | + through are not. |
| 56 | + |
| 57 | +## Alternatives Considered |
| 58 | + |
| 59 | +### Alternative 1: Maximize reuse via shared scenario builders |
| 60 | + |
| 61 | +- **Pros**: Short call sites. |
| 62 | +- **Cons**: Hides what each test owns; failures are harder to diagnose. |
| 63 | +- **Why not**: Readability of the scenario matters more than shorter setup. |
| 64 | + |
| 65 | +### Alternative 2: No shared guidance — each suite invents its own style |
| 66 | + |
| 67 | +- **Pros**: Maximum local freedom. |
| 68 | +- **Cons**: Inconsistent quality; hard to review or extend. |
| 69 | +- **Why not**: A thin shared philosophy scales better than ad-hoc patterns. |
| 70 | + |
| 71 | +## Consequences |
| 72 | + |
| 73 | +### Positive |
| 74 | + |
| 75 | +- Tests read as stories; failures point at a clear decision. |
| 76 | +- Sharing stays deliberate. |
| 77 | + |
| 78 | +### Negative |
| 79 | + |
| 80 | +- Some tests look more verbose than a “build everything” helper. |
| 81 | + |
| 82 | +### Risks |
| 83 | + |
| 84 | +- File-local helpers slowly become a private framework. |
| 85 | + **Mitigation:** extract only on repetition; prefer deleting a wrapper over |
| 86 | + stacking another. |
0 commit comments