Vitest Architecture
Portable testing house style for Vitest 4 in monorepos (and single packages). Use this skill alone — it does not depend on other skills.
Job: where tests live, how projects/scripts are split, unit vs integration.
Not this skill: Vitest API details, mocks, snapshots, or latest docs. Prefer current Vitest documentation for those.
If the target repo already documents testing (e.g. AGENTS.md) and it
conflicts, repo wins unless the user asks to migrate toward this skill.
Stack defaults (core)
| Piece | Default |
|---|---|
| Runner | Vitest 4 — never Bun’s bun test / bun:test |
| Config | Per workspace vitest.config.ts + unit/integration projects (not a monorepo-root Vitest workspace) |
| Layout | tests/unit/**, tests/integration/** |
| Default gate | Unit only (test / test:watch) |
| Empty packages | passWithNoTests so Turbo/CI stays green |
Modes
- Scaffold — add Vitest to a package from checklist.md + tree.md.
- Apply — place new tests in the right project and folder.
- Review — compare to rules.md; propose moves.
Hard rules (core)
import { … } from "vitest"(or@effect/vitestwhen that overlay applies). Neverbun:test.- Per package/app owns its Vitest configs —
vitest.config.tslists projects; do not assume a root aggregator config. - Two projects by default:
unitandintegration, selected via--project. - Default CI gate = unit. Integration is a separate script (
test:integration). - Tests live under
tests/(not colocated under everysrc/file). Name by aspect (status.test.ts), notpackage-status.test.ts. - Unit = fast — pure logic, mocks, in-memory; no required Docker for the default gate.
- Integration = real deps allowed — longer timeouts; often
fileParallelism: falsewhen sharing containers/DB. passWithNoTestson run scripts (and usually indefineConfig) so empty workspaces do not fail orchestration.- Align
vitestand@vitest/*on the same version (catalog pin when the monorepo uses a catalog).
Details: rules.md, tree.md, examples.md.
Progressive disclosure
| Need | Read |
|---|---|
| Canonical trees | references/tree.md |
| Rules + anti-patterns | references/rules.md |
| Scaffold / review checklists | references/checklist.md |
| Good vs bad layouts | references/examples.md |
| Optional overlays | Extensions below |
Extensions
Load only when the matching stack is present (or the user asks):
| When | Extension |
|---|---|
| Testcontainers / Docker in integration | with-testcontainers.md |
@effect/vitest / Layers in tests |
with-effect-testing.md |
Elysia plugin.handle(Request) |
with-elysia-handle.md |
| React + happy-dom / Testing Library | with-react-happy-dom.md |
*.test-d.ts type projects |
with-typecheck-project.md |
| Storybook + Vitest browser | with-storybook-browser.md |
Health probe + skipIf live tests |
with-live-harness.md |
| Package-local coverage thresholds | with-coverage.md |
Monorepo note
If Turbo (or similar) orchestrates scripts from the repo root, root test /
test:integration should call each package’s Vitest scripts — not a single
root Vitest project list. Task graph / transit details belong in monorepo
architecture docs for that repo.