Vitest Testing
Use the tdd skill when the task is a logic change or bug fix that should start with a failing test. This skill covers Vitest-specific test shape and review rules.
Setup
- Check
vitest.config.*before assuming globals are enabled. - If globals are enabled, use
describe,it,expect,vi,beforeEach, andassertdirectly without importing them. - If globals are not enabled, import only the Vitest APIs used by the file.
- Avoid
await import()and other dynamic imports in tests unless the behaviour under test is dynamic loading. - Use
vi.stubEnv()instead of mutatingprocess.envdirectly.
Readability
- Test one behaviour per
it. - Name tests after observable behaviour, not implementation details.
- Avoid
try/catchfor expected failures. Useexpect(...).toThrow(),await expect(...).rejects, or explicit Result failure assertions. - Avoid
ifbranches inside test bodies. Split behaviours into separate tests or useit.eachfor table-driven cases. - KISS > DRY: do not over-DRY tests. Keep repeated setup inline when it makes the behaviour easier to read.
- Keep assertions in the test unless a helper name is clearer than the assertion it hides.
- Use
assertto make preconditions explicit and narrow nullable values instead of using non-null assertions.
Output Tests
- Prefer JSON assertions for structured behaviour.
- Use file snapshots with
toMatchFileSnapshotfor human-readable CLI/table output so layout changes are reviewable. - For responsive terminal output, capture or set the terminal width used by the command.
Examples
Expected failure:
tsit('rejects invalid config', async () => { await expect(loadConfig('bad.json')).rejects.toThrow(Error); });
Table-driven case:
tsit.each([ ['daily', '2026-05-16'], ['monthly', '2026-05'], ])('groups %s rows by period', (reportType, expectedPeriod) => { const rows = groupUsage(reportType, usage); expect(rows[0]?.period).toBe(expectedPeriod); });
Narrowing with assert:
tsit('returns the first row', () => { const rows = getRows(); const firstRow = rows[0]; assert.isDefined(firstRow, 'expected at least one row'); expect(firstRow.id).toBe('row-1'); });

