Appearance
Testing
Minitest with fixtures. No RSpec, no FactoryBot.
bash
mise exec -- bin/rails test # everything
mise exec -- bin/rails test test/models # a directory
mise exec -- bin/ci # style, security, testsLayout
| Directory | Covers |
|---|---|
test/models, test/lib | Domain logic |
test/controllers | Requests, authorisation, rendering |
test/policies | Authorisation predicates |
test/helpers | Presentation mapping |
test/invariants | Product rules that must hold everywhere |
test/design_audit | Real-browser screenshots and Turbo interaction, skipped unless DESIGN_AUDIT=1 |
Do not write down what the code does
Write the assertion you would have written before reading the implementation. A test that records observed output is not a test — it is a lock, and if the code is wrong it locks the wrongness in.
This has happened five times in this codebase:
| Asserted | Should have been |
|---|---|
Both screening gates clear when no screening had run | not_evaluable — nobody looked |
match_count: 0 for a PENDING_REVIEW body | nil — the provider had not answered |
403 for another account's record | 404 — 403-vs-404 enumerates tenants |
medium_max == scale_max is valid | Rejected — it makes the high band unreachable |
A risk_level with no risk_level_updated_at is published | Withheld — the column defaults to low |
They share a mechanism: the case had no obviously right answer, so the value the code produced got written down as the expectation. Code and test then agreed — which is exactly why mutating a single file never caught them. The test was wrong in the same direction.
The rule for this domain: whenever an assertion says a check passed, a count was zero, a score banded or a record was clean, ask what was actually read to justify it. If the answer is "nothing", the expected value is an absence — nil, not_evaluable, "not assessed" — never a reassuring one.
Invariants
test/invariants/ states product rules as executable assertions that walk the real registries — every gate, every public-result adapter, every provenance value — so a component added tomorrow is covered without anyone remembering.
Currently held. never_claim_clear_test.rb carries the core family — the product principle applied layer by layer:
- No gate reports a clean outcome from evidence nobody read, and neither does the person readout
- No provider adapter reports a positive result from a payload that says nothing
- A screening that has not answered has no match count
- A nil score never bands, under any configuration
- No provenance lets a below-neutral score through from an unevidenced source
- No valid tier configuration makes a band unreachable
- A date of birth is never held or sent in the wrong calendar
Each remaining file states one principle:
| Principle | File |
|---|---|
| A date in the wrong calendar is not a date of birth, on any surface reporting one | a_date_in_the_wrong_calendar_is_not_a_date_test.rb |
| The API never claims a verification that never happened | api_never_claims_clear_test.rb |
| A country scope that is advertised is a country scope that is enforced | country_scope_is_enforced_test.rb |
| A check nobody can configure is a check nobody runs | every_check_reaches_the_country_defaults_test.rb |
| Every permission is filed and named, in every language | permission_catalog_covers_every_permission_test.rb |
| A setting the code supplies is never a setting an operator has to | required_config_is_obtainable_test.rb |
| The role catalogue is a compliance artefact, and no role is both maker and checker | role_catalogue_test.rb |
| Screening never searches nothing and calls it clean | screening_never_searches_nothing_test.rb |
| A value an operator can set is a value the request carries | settings_reach_the_request_test.rb |
See test/invariants/README.md, which is the index these are kept against.
The first run of that suite found a live bug nobody had reported: low_max could be set below the bottom of the scale, leaving the low band unreachable.
When you fix a bug whose test agreed with it, add an invariant rather than only correcting the case.
Mutation-check anything load-bearing
Break the implementation deliberately and confirm the test fails. A green invariant proves nothing until you have seen it go red — one gate invariant here looked correct and was empty, because its fixture tripped an earlier guard and never reached the code under test.
Fixtures
Descriptive names, never one / two. Watch for fixtures that encode a bug: four of the five cases above were fixture-level, including one that reused Elm's returned birth date as the subject's stored claim, conflating what we send with what comes back.
Design audit
bash
DESIGN_AUDIT=1 mise exec -- bin/rails test test/design_audit/Boots headless Chromium against a disposable seeded database and writes full-page screenshots at 1280px and 390px into tmp/design_audit/. Useful for reviewing a visual change across every screen at once.
Known flake
This suite fails intermittently — roughly one run in three at times — and the cause is not yet identified. If you see a failure here, re-run before investigating the change you just made.