Internal: Vitest rules (tests per layer, fakes over vi.mock, MSW); read by work's executors before *.test.ts(x) and vitest.config.
Resources
1Install
npx skillscat add idontlikedrinkpivo/web-dev-pipeline-codex-plugin/vitest Install via the SkillsCat registry.
Codex runtime. Before using this skill, read
../executor-catalog/references/codex-runtime.md. It defines tool mappings, named-agent dispatch and stage-chat orchestration; use it for inherited Claude Code terms below.
Vitest
Which layering applies. The cases (architecture document, Framework-first, existing repo) are intypescript/references/which-layout-governs.md, and the rules every test suite shares (mapper round-trip, race
test, property test, grouping, mutants, no database reachable) in typescript/references/shared-test-rules.md —
read both first. Vitest's placement in the cases:
- An architecture document exists — the foundation §6 test plan:
- one entity test per row of the domain model's invariant table (
domain.md§4), no I/O; - use-case tests with in-memory fakes of the ports;
- repository adapter tests against a real Postgres;
- one round-trip test per mapper;
- HTTP tests through the server's in-process injector (Fastify
app.inject), one of them an end-to-end path.
- one entity test per row of the domain model's invariant table (
- Framework-first — no ports to fake: the one
domain/module gets plain unit tests, the rest is tested
through the in-process injector against the real database. - Existing repo — mirror its placement, naming, setup files and fakes (colocated
*.test.tswhen there is no
precedent).
The machine checks — keep them green
@vitest/eslint-plugin (repo-scaffold/references/quality-gates.md); the prose below keeps only what lint cannot
see.
| Rule | Lint |
|---|---|
every .resolves / .rejects is awaited or returned |
vitest/valid-expect |
no committed .only |
vitest/no-focused-tests |
| a skipped test is flagged (its reason is the reviewer's) | vitest/no-disabled-tests |
| every test asserts something | vitest/expect-expect |
component tests query by role and label, act with userEvent |
testing-library/* (react → The machine checks) |
House rules
- Read neighbouring tests and the config first; one style per suite.
- Fake only what crosses the process boundary (network, clock, randomness, file system). Pass a fake where the code accepts one instead of
vi.mockof a module; a test of a mocked internal proves the mock. A fake port is an in-memory store that behaves like the real adapter, and the test asserts what was stored and returned — not which method was called how often, unless the call order is itself a stated rule. - One behaviour per test, named after behaviour and condition; edge cases and the error path beside the happy path, including exact boundary values.
describeblocks and shared setup: shared rule 4. vi.mock/vi.hoistedonly at module top level.- Undo what a test changed in
afterEach: real timers back, spies restored, mock state reset. Fake time instead of sleeping. No mutable state shared between tests; the file must pass in shuffled order. - Run the file, then break the code under test once and watch the test fail.
- A
.skipcarries why and when it returns. Read every snapshot diff before accepting it. - Property tests with
fast-check(shared rule 3). - A guard against a double action gets the race test of shared rule 2:
Promise.allof two calls, each with its
own connection from the pool. - A surviving mutant gets the assertion that kills it (shared rule 5).
- The test database is named, never defaulted: the database helper reads
TEST_DATABASE_URLand throws
when it is unset, when its database name has no «test», or, outside CI (CIunset), when it equalsDATABASE_URL
— locally it is a database of its own, not the one a running stand serves; a fallback address
once sent these tests to a running stand and wiped its data. No database reachable here: shared rule 6. - Coverage has one threshold, in
coverage.thresholdsof the Vitest config (the foundation §6 number, otherwise 80% of lines); the full suite measures it in CI (repo-scaffold/references/quality-gates.md, the Coverage gate), a unit's focused run does not.
A whole-application browser flow (log in, open a page, type, click, see the result) is Playwright, not Vitest:
route it to playwright-cli. A component test is Vitest with Testing Library (jsdom, or Browser Mode where the
project uses it): render through the shared renderWithProviders, mock the network with MSW, query by role and
label, act with userEvent, wait with findBy* — the rules are react → The reviewer checks, item 15.
Version-sensitive points — references/version-checks.md. In a work packet
their answers come in CURRENT LIB DOCS; where it says none, probe the installed version in the project instead
of fetching docs. Outside work, read the file and check each point you rely on against the installed version.
The reviewer checks
- A fake of a port, never
vi.mockof the project's own module or a mocked database client; the test asserts
what was stored and returned, not call counts. - What a test changed is undone in
afterEach; no state shared between tests. - A guard against a double action has a race test against the real database — two calls, each on its own
connection, the effect counted. - The error path and the exact boundary values sit beside the happy path, one behaviour per test,
describe
by behaviour, never by plan unit. - A
.skipsays why and when it returns.