Files
atomic-design-poc/.claude/skills/test-strategy/SKILL.md
T
ehoandClaude Sonnet 5 a82332fa20 docs: ADR-0006 test-data builders, close out WP-70
Writes up the principle behind WP-70's three tracks ("build test data
through the same door production code uses") as ADR-0006, with a decision
table for which fixture idiom fits which test type. Updates the
test-strategy skill (adds the Fixtures rule, fixes its stale pre-monorepo
src/app/... worked-example paths) and the shared Storybook testing.mdx page
to match. Closes WP-70 with the signatures/counts as actually shipped.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-18 15:31:20 +02:00

3.5 KiB

name, description
name description
test-strategy Place tests the house way — Vitest specs co-located by layer (pure domain, no TestBed; parse* trust boundaries; thin UI via Storybook a11y). Use whenever adding a spec or deciding what to test.

Test strategy (test where it's pure)

Push logic down to where it's pure, test it there directly, keep the layers above thin. No TestBed for domain. Never assert on user-facing copy.

Rules

  • domain/ + any pure logic → required spec. Reducers, combinators, visibleSteps, policies, parsers. Import the function and call it — no Angular, no TestBed.
  • Value-object parser → happy path + normalisation + each rejection. Assert on the Result discriminant (.ok) and the parsed value, not the error message.
  • infrastructure/ parse* (trust boundary) → required spec. Accept a valid DTO; reject null, {}, and malformed shapes. Name it describe('… (trust boundary)').
  • application/ stores/commands → spec the pure reduce + optimistic begin→confirm/rollback + the command Result.
  • ui/ → Storybook story, not a component test. Axe runs on every story; add a play only for wiring axe can't see.
  • Never assert on $localize copy. It changes per locale/edit — assert on the Result, the value object, or the message id.
  • Fixtures go through the production door, never a hand-built literal (ADR-0006). Replay real Msgs through the real reduce (given(reduce, initial)(...msgs)) for a state machine; unwrapOk(parseX(raw)) for a value object; a type-state builder (Given.Concept().Submitted()...) for a backend aggregate with an ordered lifecycle. The one deliberate exception is a trust-boundary parse* spec, below — there the fixture must be a raw, possibly-malformed literal, because the test's whole point is "what if this shape is wrong." See ADR-0006's decision table for which idiom fits which test type.

Skeleton

Co-locate *.spec.ts next to the unit, in the same layer folder:

<context>/domain/<thing>.spec.ts            # pure — no TestBed
<context>/domain/value-objects/<vo>.spec.ts # parser: ok + normalise + each reject
<context>/infrastructure/<x>.adapter.spec.ts# parse* trust boundary
<context>/application/<store|command>.spec.ts

Minimal parser spec:

import { describe, it, expect } from 'vitest';
import { parseThing } from './thing';

describe('parseThing', () => {
  it('accepts + normalises', () => {
    const r = parseThing(' raw ');
    expect(r.ok).toBe(true);
    if (r.ok) expect(r.value).toBe('RAW');
  });
  it('rejects malformed', () => {
    expect(parseThing('').ok).toBe(false); // asserts the tag, not the copy
  });
});

Worked examples

  • apps/ssp/src/app/registratie/domain/value-objects/postcode.spec.ts — parser style.
  • apps/ssp/src/app/registratie/infrastructure/brp.adapter.spec.ts — trust boundary (null/{}).
  • apps/ssp/src/app/registratie/domain/registratie-wizard.machine.spec.ts — pure reducer.
  • libs/shared/src/testing/{machine,remote-data,value-object}.ts — the shared fixture helpers (ADR-0006); apps/ssp/src/app/herregistratie/domain/intake.testing.ts — a per-context wrapper (givenIntake = given(reduce, initial)); intake.acceptance.spec.ts — a full journey expressed as one replayed message sequence.

Verify

npm test                     # Vitest (ng test — no vitest.config)
npm run test-storybook       # axe over every story (UI a11y gate)
cd backend && dotnet test    # backend rule + endpoint + golden tests