Claude Code writes tests fast. That’s the problem as often as it’s the benefit. Without explicit rules, it defaults to whatever gets a green checkmark fastest — which sometimes means asserting expect(true).toBe(true), mocking the exact function under test, or generating a snapshot test that locks in a bug forever. A CLAUDE.md section for testing exists to close that gap.
We reviewed public CLAUDE.md and AGENTS.md files that configure Jest, Vitest, and Playwright, cross-referenced them against Anthropic’s own testing guidance, and pulled out what actually holds up across real projects. Below is a template, a framework comparison, and the failure modes we see most often when agents write their own tests unsupervised.
Why Claude Code Needs Explicit Testing Rules
Every coding agent, Claude Code included, is optimized to complete the task it was given. If the task is “make the tests pass,” the shortest path to that goal isn’t always “write a correct test.” Left unconstrained, agents gravitate toward three shortcuts:
Tautological assertions. A test that asserts a value against itself, or checks that a function didn’t throw without checking what it returned. It passes immediately and catches nothing.
Over-mocking. Mocking the exact unit under test, or mocking so much of the surrounding system that the test only verifies the mocks were called — not that the code works.
Snapshot sprawl. Generating a snapshot test for anything with complex output, then updating the snapshot the moment it fails instead of asking whether the new output is correct.
None of these are malicious. They’re the rational output of an agent that was told “add tests” without being told what a passing test is supposed to prove. A CLAUDE.md testing section fixes this by making the definition of “done” explicit before Claude writes a single it() block.
Choosing a Framework: Jest vs Vitest vs Playwright
These three cover different layers, and a good CLAUDE.md tells Claude Code which one to reach for instead of letting it guess from whatever’s already in package.json.
| Framework | Layer | When to tell Claude to use it |
|---|---|---|
| Jest | Unit / integration | Existing CRA, Next.js (Pages Router), or Node projects already on Jest. Mature mocking API (jest.mock), broad ecosystem. |
| Vitest | Unit / integration | New TypeScript/Vite/Next.js (App Router) projects. Near-identical API to Jest but faster, native ESM, and shares config with your Vite build. |
| Playwright | End-to-end | Anything that needs a real browser: user flows, visual regression, cross-browser checks. Not a Jest/Vitest replacement — they compose. |
The most common mistake we see in agent-written CLAUDE.md files is not naming a framework at all, just saying “add tests.” Claude Code will then check package.json, and if it finds none, install whichever framework it defaults to — which is not always the one the rest of the team standardized on. State the framework explicitly, even if it feels redundant with your package.json.
Complete CLAUDE.md Template for Testing
This template assumes Vitest for unit tests and Playwright for e2e — swap the Vitest section for Jest if that’s your stack; the surrounding rules apply either way.
## Testing
### Stack
- Unit/integration: Vitest (`vitest run`)
- E2E: Playwright (`playwright test`)
- Do not install Jest, Mocha, or Cypress unless explicitly requested — this project standardizes on Vitest + Playwright.
### What a passing test must prove
- Every `expect()` must reference a value produced by the code under test, not a hardcoded literal compared to itself.
- Do not write `expect(true).toBe(true)`, `expect(fn).not.toThrow()` alone, or any assertion that would pass if the implementation were deleted.
- When a test fails, fix the code or the test — never widen an assertion just to make it pass.
### Mocking discipline
- Never mock the function or module directly under test.
- Mock at the boundary: network calls (`fetch`, API clients), file system, timers, and third-party SDKs — not internal application logic.
- If a test requires mocking more than 2 internal dependencies to run, treat that as a signal the unit is too large — flag it instead of mocking around it.
### Coverage and scope
- New business logic requires unit tests for: the happy path, at least one error path, and boundary conditions (empty input, null, max length).
- Bug fixes require a regression test that fails on the old code and passes on the new code — write it before the fix when possible.
- Do not chase 100% coverage by testing getters/setters or framework boilerplate.
### Snapshots
- Snapshot tests are allowed only for stable, human-reviewable output (rendered markup, serialized config).
- Never run `--update-snapshot` / `toMatchSnapshot()` update without showing the diff and explaining what changed.
### E2E (Playwright)
- Use role-based and text locators (`getByRole`, `getByText`) — not CSS selectors or `data-testid` unless the element has no accessible role.
- Every `page.click()` / `page.fill()` must be preceded by an explicit wait condition, not a fixed `waitForTimeout`.
- E2E tests run against a seeded test database or mocked API layer — never against production or shared staging data.
### Commands
- Run unit tests: `npm run test:unit`
- Run e2e tests: `npm run test:e2e`
- Run a single test file: `npx vitest run path/to/file.test.ts`
- Before marking any task complete: run the relevant test suite and report the result, not just "tests added."
The “what a passing test must prove” section is the one most templates skip, and it’s the one that prevents the tautological-assertion problem described above. Stating it explicitly changes what Claude Code considers finished.
Playwright’s Planner, Generator, and Healer Agents
Playwright shipped three specialized subagents in 2026 — planner, generator, and healer — designed to work alongside Claude Code’s own subagent system rather than as a standalone tool. This changes how you should scope Playwright work in CLAUDE.md:
- The planner breaks a feature or user flow into discrete test scenarios before any code is written. If your
CLAUDE.mdcurrently tells Claude to “write e2e tests for the checkout flow” in one shot, splitting that into a planning pass first produces measurably more complete coverage. - The generator knows Playwright’s locator and waiting conventions natively, which is why the rules above (role-based locators, explicit waits) matter less as manual policing and more as a check on generator output.
- The healer re-diagnoses a failing selector when the DOM changes, instead of Claude Code blindly widening the selector or adding a
waitForTimeoutto paper over a flaky test.
If you’re running Playwright’s agents inside a Claude Code subagent workflow, add one line to your testing rules: Delegate e2e test planning to the Playwright planner agent before generating test code. That single instruction is enough to route the work through the right tool instead of having Claude Code’s main context write ad hoc Playwright scripts from scratch.
Enforcing Rules with a PostToolUse Hook
Rules in CLAUDE.md are read, not enforced. A hook makes the test suite actually run after Claude edits test files, instead of relying on Claude to remember.
{
"hooks": {
"PostToolUse": [
{
"matcher": "Edit|Write",
"hooks": [
{
"type": "command",
"command": "if git diff --name-only HEAD | grep -qE '\\.(test|spec)\\.(ts|tsx|js)$'; then npx vitest run --changed; fi"
}
]
}
]
}
}
This runs Vitest against changed test files only, on every edit — not the full suite, which keeps the loop fast enough that it doesn’t get in the way. Swap vitest run --changed for jest --onlyChanged if you’re on Jest, or point it at playwright test with a .spec.ts file filter for e2e-only hooks.
Common AI Testing Mistakes to Watch For
Even with the rules above in place, these show up often enough to name explicitly in review:
Testing the mock, not the code. A test that mocks a database call and then asserts the mock was called with the right arguments — without ever exercising the actual query logic — passes even if the real query is broken.
Deleting a failing test instead of fixing it. When a pre-existing test starts failing after a change, agents sometimes remove or skip it (it.skip) rather than investigate whether the change broke real behavior. Flag any .skip() or deleted test in review.
Flaky Playwright selectors that “pass on retry.” A selector that only works after Playwright’s automatic retry logic kicks in usually means the wait condition is wrong, not that the test is fine. Retries hiding a real timing bug is one of the most common false-positive patterns in AI-generated e2e tests.
Coverage theater. Tests written to hit a coverage percentage rather than to verify behavior — testing a getter, testing that a constant equals itself, testing framework-generated boilerplate. High coverage numbers with low mutation-testing scores are the tell.
None of this requires a testing framework switch or new tooling. A CLAUDE.md testing section, a coverage-scoped PostToolUse hook, and — if you’re on Playwright — routing e2e work through its planner/generator/healer agents are enough to move Claude Code from “adds tests to satisfy the prompt” to “adds tests that would catch a regression.”
Browse working CLAUDE.md and AGENTS.md examples with testing sections from real projects in our gallery.
FAQ
Does Claude Code run tests automatically after making changes?
Not by default. Claude Code will run whatever command you ask it to, and models often run tests proactively for changes they just made, but this isn’t guaranteed. A PostToolUse hook, like the one above, makes it deterministic instead of relying on the model’s judgment.
Should I use Jest or Vitest for a new project in 2026? For a new TypeScript project, especially one already on Vite or Next.js App Router, Vitest is the more common default — it shares config with your build tool and runs faster. Jest remains the safer choice for existing codebases with heavy investment in its mocking ecosystem or projects on older bundler setups.
Can Playwright’s planner/generator/healer agents replace my CLAUDE.md testing rules?
No — they operate at a different layer. The agents handle test planning and generation mechanics; your CLAUDE.md rules define what counts as a valid test (no tautological assertions, mocking boundaries, coverage scope) regardless of which tool wrote it.
How do I stop Claude Code from writing snapshot tests for everything? Add an explicit rule scoping snapshots to specific output types (rendered markup, serialized config) and require a shown diff before any snapshot update. Without that constraint, snapshot tests are the path of least resistance for any output an agent doesn’t want to write real assertions for.
What’s the minimum testing section if I don’t want the full template? Three rules cover most of the risk: name the framework explicitly, forbid mocking the unit under test, and require that bug-fix commits include a regression test. Everything else in the template above is refinement on top of those three.