Skip to content

Unit Testing

Smallest unit, fastest feedback. Single function or module, milliseconds to run. The chain's first catch — and the layer where domain logic is written down twice.

Owners: Developer Phase it lives in: How We Build (Volume IV) The corpus principle this enacts: Unit tests are executable specs.

Where it lives in the chain

How to do this

  • Practice — name the test after the scenario from amigos, not the implementation. gradeExam_clamps_balance_at_zero_for_negative_input, not test_gradeExam_3.
  • Practice — one assertion per test where possible. If the test fails, the failure message tells the developer which behaviour broke without running the debugger.
  • Practice — pure domain functions are tested without mocks. A unit test that needs three mocks to run is testing the wrong unit.

What good practice looks like

The unit test reads like the scenario. The scenario said:

gherkin
Given Gal has an exam with three negative-scored answers
When she submits the exam
Then her balance is clamped at zero and surfaced in red

The test:

ts
it('clamps negative balance at zero when submitting an exam', () => {
  const exam = examWith({ scores: [-5, -3, -2] })
  expect(submitExam(exam).balance).toBe(0)
})

The test name is the spec. The body is the proof. When the wallet-balance bug surfaces, the test that should have caught it is the test that was never written — that absence is what the chain-aware label scenario-gap names.

The discipline: the unit test layer holds the brief's logic; the integration layer holds the brief's flow; the contract layer holds the brief's promise to other systems. Each layer catches a different chain level.

200apps · How We Work · NWIRE