Skip to content
Skill · Cursor.cursor/rules/*.mdcCoding

Vitest Test Authoring Rules

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.

@libraryNewcomer
0 upvotes0 forks0 comments

The skill file

Cursor · .cursor/rules/*.mdc
.cursor/rules/*.mdc3139 chars
--- 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.

Install

drop into your repo

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

path./.cursor/rules/testing-vitest-rules.mdc
Discussion

What people are saying

Forks

0 forks of this skill
Loading forks…