claude-code lastVerified: 2026-05-08

Claude Code Test Generation Prompt — Vitest + Behavior-First Template

A Claude Code prompt for generating Vitest unit, integration, and component tests. Tests behavior over implementation. Vite + React + TypeScript ready.

On this page
  1. Why behaviour-first testing matters
  2. The prompt
  3. Why each section earns its place
  4. Variants by test type
  5. Common pitfalls when Claude Code writes tests

Why behaviour-first testing matters

A Vitest suite that snapshots useState internals breaks every refactor and tells you nothing about whether the user-facing surface still works. A Vitest suite that asserts on rendered output, on the resolved value of a fetch, and on the side effects of a click survives internal restructuring and catches the regressions that actually ship. Claude Code, unprompted, will write the first kind because it is what its training data is full of — implementation-pinned tests that look thorough on disk and fall over the first time someone extracts a hook. This prompt forces it to write the second kind: assertions on what the exported behaviour does, not on which hook stores the state.

The prompt

test-generation-prompt.md
# Test goal
Write Vitest tests for <module / component path>. Every assertion must
target exported behaviour or rendered output — never an internal hook,
private function, or implementation detail. Use Testing Library queries
that map to user perception (getByRole, getByLabelText, getByText) over
queries that target DOM internals (getByTestId).

# Required structure

- Group with `describe(<unit-under-test>, () => { ... })`
- One `it(...)` per behavioural claim, named "does X when Y"
- Arrange / act / assert visible inside each test
- Mock at the module boundary with `vi.mock(...)`; do not stub internals

# Determinism rules

- No real network: `vi.mock` fetch / axios / the API client module
- No real timers: `vi.useFakeTimers()` and advance explicitly
- No randomness: seed Math.random or stub the helper that calls it
- No date drift: mock Date.now() if the code reads the wall clock

# Coverage targets

- Happy path: at least one positive assertion
- Edge cases: empty input, null input, error response
- Re-render stability: identical input twice produces identical output
- Cleanup: any subscription / timer / event listener tears down

# Output format

1. The test file in full (full source, ready to drop into the repo).
2. The exact `pnpm test --run <file>` command and its truncated output.
3. One sentence per coverage target confirming a test exists for it.

Why each section earns its place

The Test goal block carries the binding rule: assertions target exported behaviour. Naming Testing Library queries explicitly is what stops Claude Code from reaching for data-testid sprinkles that turn the test file into a pinning exercise. The Required structure block enforces a shape a human can read at a glance — describe-then-it, arrange-act-assert visible per test. The Determinism rules block is where most generated tests fail in CI even when they pass locally; mocking at the module boundary plus fake timers plus seeded random is the trio that makes a test pass on the second run, the third run, and the run a year from now. The Coverage targets block is short on purpose — four bullets, each non-negotiable, each easy for the agent to self-grade against.

Variants by test type

Pick the row that matches the unit under test. The verification command is what runs after the file is written — a passing run is the contract.

Test typeFrameworkVerification command
UnitVitestpnpm test --run src/lib/<file>.test.ts
IntegrationVitest + Testing Librarypnpm test --run src/features/<dir>
ComponentVitest + React Testing Libpnpm test --run src/components/<file>
Playwright E2EPlaywright (separate suite)pnpm exec playwright test --project chrome
RegressionVitestpnpm test --run src/regressions/<file>
Bug-reproductionVitestpnpm test --run --reporter=verbose <file>

For component tests, Testing Library’s getByRole should be the default and getByTestId the last resort; if a piece of UI cannot be reached by role, that is itself an accessibility finding worth flagging back through the code review prompt. For Playwright, Claude Code’s default is to over-mock the network and miss the real CSP / cookie / redirect issues — the prompt above forbids real network for unit and integration but Playwright projects need the network alive against a staging URL.

Common pitfalls when Claude Code writes tests

The agent’s three favourite anti-patterns are: testing useState directly via renderHook even when the hook is private to the component — counter by demanding the test mount the component and assert against rendered output. Snapshotting the entire DOM tree when one assertion would do — counter by stating “no toMatchSnapshot; explicit assertions only” in the prompt. Mocking at the wrong layer — for instance, stubbing useEffect instead of stubbing the API module the effect calls — counter by naming the exact module to mock at the boundary. Lock these decisions into CLAUDE.md so they apply across every test conversation, not only when you remember to paste the prompt. For the upstream tasks tests verify, see the feature prompt, the bug fix prompt, and the refactor prompt — tests written with this prompt are the safety net all three rely on.