/test-unit
The reference slash command for framework-aware unit test generation. Auto-detects Vitest/Jest/pytest/Go test, matches your team's existing style, colocates correctly, refuses to over-mock. Pairs naturally with /commit.
sonnet/test-unit is the most-installed testing command after /commit is the most-installed git command. Same reason: writing unit tests is a repetitive-shape task that happens many times per day, and codifying it into a well-tuned slash command produces tests that match your project's style, use the right framework, and colocate correctly — without you having to remember which test framework this repo uses or where its test files live.
The reference implementation on this page does four things a naive test-writing prompt wouldn't. It detects the framework from package.json (or pyproject.toml, or go.mod) rather than guessing. It matches your team's existing test style by reading a nearby test file — naming pattern, assertion style, mock style. It colocates correctly (adjacent .test.ts vs __tests__/ folder vs test/ directory), based on what your project already does. And it refuses to over-mock, defaulting to real implementations for anything that isn't external I/O.
Install /commit first. Install /test-unit second. Those two commands together cover a huge chunk of daily developer work in Claude Code.
The full /test-unit definition
Copy this file to .claude/commands/test-unit.md. The framework detection and colocation logic work across JS/TS, Python, Go, and Rust projects out of the box.
How to install
Same install pattern as /commit — save the file, commit it, done.
Project scope Recommended
Save as .claude/commands/test-unit.md in your repo. Commit to git. Every teammate gets identical test-writing behavior.
User scope Personal
Save as ~/.claude/commands/test-unit.md. Works across every project you touch. Useful if you often work solo on unfamiliar codebases and want a consistent testing helper.
Both scopes
Project scope wins on conflicts. Common pattern: user-scope /test-unit as your personal default, project-scope override for repos with unusual test conventions (e.g., E2E only, mandatory 90% coverage, specific fixture patterns).
How to use it
The command takes a file path plus optional focus. Framework detection and colocation happen automatically.
src/utils/date-format.test.ts covering the public API. Total elapsed: ~15 seconds.pyproject.toml, matches the project's assert-vs-self.assert style, colocates the test file per project convention.go.mod, uses table-driven test pattern (the Go idiomatic style), creates internal/auth/session_test.go adjacent to the source file per Go convention.Alternative configs
Adjust for framework-specific preferences or team-specific patterns.
e2e/ use Playwright. For files under src/**/*.ts use Vitest. For files under src/**/*.spec.ts use Jest." Explicit rules beat auto-detection when your project deliberately uses multiple frameworks.pnpm test --coverage $ARGUMENTS and confirm coverage on the target file is >= 80%. If not, add tests until it is or explain why the uncovered lines are legitimately hard to test." Adds a self-check loop.Pairs well with
Testing works best as a system — a command that writes tests, a hook that runs them, a subagent for coverage analysis, and an MCP for real error data.
Common issues and fixes
The five issues teams hit most often with /test-unit.
.test.<ext> suffix. Do NOT create __tests__ subfolders." Or the opposite — whichever your project uses. Explicit beats inferred.allowed-tools Write patterns.Write() list in the frontmatter. Common additions: Write(**/spec/**) for Rails-style, Write(**/test_*.py) for pytest with test_ prefix convention. The narrow allowed-tools is the safety mechanism; keep it narrow but complete.Other testing items
Same category, different job.
🐛 Hit an error?
AI Error Hub covers Claude Code errors and stack traces — cross-referenced with everything on this site.
Frequently asked questions
The questions developers ask most about /test-unit.
Four differences:
- Framework detection reads real
package.json(not guessed from imports). - Style-matching reads a nearby existing test file (not invented).
- Colocation follows what the project already does (not arbitrary).
- Mock discipline is enforced by rule (not left to Claude's discretion).
The naive prompt version works too — but produces tests that need cleanup half the time. The command produces tests that don't.
The command reads the target file's nearest test-file neighbor and matches that framework.
So a project with both Jest (for legacy) and Vitest (for new code) works correctly — files near existing Jest tests get Jest tests, files near Vitest tests get Vitest.
If auto-detection picks wrong, add an explicit override rule to the command body, one line per directory boundary: "Under src/legacy/** use Jest. Under src/** (elsewhere) use Vitest."
Sonnet is right. Writing unit tests is applied text generation with clear rules — Sonnet handles it well.
Opus adds cost without meaningfully better output for this task. Reserve Opus for genuinely analytical work (security review, schema design, architectural decisions).
Read + Grep + Glob for exploration, narrow Write for test file locations, and Bash restricted to reading manifest files:
Read,Grep,GlobWrite(**/*.test.*),Write(**/*.spec.*),Write(**/__tests__/**),Write(**/tests/**),Write(**/test/**)Bash(cat package.json),Bash(cat pyproject.toml),Bash(cat go.mod)
Do not give general Bash.
It reads the target file to understand the public surface (exported functions, classes, module APIs) and generates tests covering:
- Happy paths — the intended usage.
- Edge cases — empty input, boundary values, unicode.
- Error paths — invalid input, throws.
- Observable state changes — if applicable.
Private implementation details are skipped. Passing a focus argument (edge cases, error handling) shifts the balance.
Real tests. That's the point of the style-matching and no-over-mock rules.
Generated tests use your team's assertion style, your existing utilities, and real dependencies wherever practical. They should be indistinguishable from tests a teammate wrote.
If they consistently need cleanup after generation, that's a signal — usually a style rule missing from the command body. Fix the command, not the output.
By default it doesn't check coverage — it writes tests it thinks provide good coverage of the target.
For teams with a hard coverage bar, two approaches:
- Use the coverage-target variant above that adds a self-check step to
/test-unit. - Chain with
/coverage-checkas a separate step.
Most teams prefer the second approach — separation of concerns keeps /test-unit fast and /coverage-check as a review gate.
Not by default — the allowed-tools Write patterns only include test file paths, not source paths. That's intentional: /test-unit writes tests, doesn't modify source.
If the target file needs refactoring to be testable (tightly coupled to global state, hard-to-mock singletons), the command reports what's blocking testability and asks you to refactor first.
Separation of concerns keeps the command safe and predictable.
Usually not. The framework-detection logic in the reference /test-unit handles the multi-framework case cleanly for most projects.
Install variants only if your project explicitly needs a specific framework regardless of what's nearby — e.g., "all new code uses Vitest even in directories with legacy Jest tests." In that case a dedicated /test-unit-vitest is a useful override.
Commit .claude/commands/test-unit.md to git. Every teammate gets identical test-writing behavior on next pull.
This is exactly the pattern that makes Claude Code output indistinguishable from human output — no per-developer drift, no "why did Claude write it that way" debates in review.
Get the weekly Claude Code digest
Every Tuesday: new testing, Anthropic release recap, and the best community submission. 13,000+ developers read it.