Skip to content

Vitest Test Authoring Rules

Cursor.cursor/rules/*.mdcCoding

Test-writing rules enforcing arrange-act-assert structure, behavior-based naming, strict mock boundaries, and minimal snapshots.

Use when: Attach to test files in Vitest projects to keep specs readable, deterministic, and focused on behavior rather than implementation details.

@library0 upvotes0 remixes0 comments

Get this skill

Add the skill file with the CLI, by hand, or over MCP.

Save this to your project or home directory so Cursor can load it.

npx goodprompts add testing-vitest-rules
Saves to./.cursor/rules/testing-vitest-rules.mdc
npx goodprompts add testing-vitest-rules
Saves to ./.cursor/rules/testing-vitest-rules.mdc

Skill file

.cursor/rules/*.mdc3,139 characters
---
description: Conventions for writing and structuring Vitest tests
globs: "**/*.test.*"
alwaysApply: false
---
# Vitest rules
## Structure: arrange, act, assert
- Every test body reads in three blocks — setup, one action, assertions — separated by blank lines. No assertions interleaved with setup, no second "act" in the same test.
- One behavior per test. If the name needs "and", split it. Multiple `expect`s are fine when they describe one outcome (status + payload).
- `describe` blocks name the unit (`describe("createInvoice")`), nested `describe` for a scenario group (`describe("when the customer has no payment method")`). Max two levels of nesting.
- Shared setup goes in plain builder functions (`makeUser(overrides)`), not sprawling `beforeEach` mutation of suite-level `let` variables. `beforeEach` is for resets (`vi.restoreAllMocks()`, fake timers), not for building test data.
## Naming
- Test names state behavior and condition, present tense, no "should": `it("returns 404 when the slug does not exist")`, `it("trims whitespace before validating the email")`.
- Banned names: `it("works")`, `it("handles errors")`, `it("test createUser")`. If you can't name the expected behavior, you don't know what you're testing.
- Test file sits next to the unit it tests: `slug.ts` → `slug.test.ts`. Integration tests that span modules go in `__tests__/`.
## Mock boundaries
- Mock only at process boundaries: network (`fetch`/HTTP clients), database, filesystem, clock, randomness, environment. Never mock a sibling function in the module under test — if you need to, the module is doing too much; split it.
- Prefer dependency injection over `vi.mock` module patching. Reach for `vi.mock` only for third-party modules with no injection seam, and keep the factory minimal.
- Every `vi.fn()` you assert on must assert arguments, not just `toHaveBeenCalled()`. Call-count-only assertions test wiring, not behavior.
- Time and randomness are always controlled: `vi.useFakeTimers()` + `vi.setSystemTime()` for anything date-dependent; never let `Date.now()` leak into an assertion.
- `vi.restoreAllMocks()` in `afterEach` (or `restoreMocks: true` in config). A test must pass when run alone and in any order.
## Snapshots
- Inline snapshots (`toMatchInlineSnapshot`) only, max ~10 lines, and only for stable serialized shapes: error messages, generated SQL, small config objects.
- Never snapshot rendered component trees, full API responses, or anything with IDs/timestamps. A snapshot nobody can review by eye is a rubber stamp — replace it with explicit `expect` on the fields that matter.
- If a snapshot fails and your instinct is `--update` without reading the diff, the snapshot should not exist.
## Assertions
- Use the narrowest matcher: `toBe` for primitives, `toEqual` for objects, `toMatchObject` when extra keys are irrelevant, `toStrictEqual` when they are not.
- Async errors: `await expect(fn()).rejects.toThrow(NotFoundError)` — never try/catch with a boolean flag, never an assertion-free `.catch`.
- No conditional logic (`if`, loops with branching) inside a test. Parameterize with `it.each` instead.
Comments

Comments

Sign in to commentSign in

Remixes

Loading remixes…