/test-unit — Claude Code Slash Command for Framework-Aware Unit Tests | AI Code Toolkit
Second most-installed commandModel: SonnetMulti-framework

/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.

Category
Testing
Model
sonnet
Framework
Auto-detected
Cost per run
~$0.008
Updated
Aug 2026

/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 Config

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.

markdown .claude/commands/test-unit.md
--- allowed-tools: Read, Grep, Glob, Write(**/*.test.*), Write(**/*.spec.*), Write(**/__tests__/**), Write(**/tests/**), Write(**/test/**), Bash(cat package.json), Bash(cat pyproject.toml), Bash(cat go.mod) description: Write unit tests for a file, matching the project's existing test style and framework model: sonnet argument-hint: [path/to/file] [optional focus, e.g. "edge cases"] --- # Context - Target file: $ARGUMENTS - Nearby test files (style reference): !`find $(dirname $ARGUMENTS 2>/dev/null || echo .) -name "*.test.*" -o -name "*.spec.*" -o -path "*/__tests__/*" 2>/dev/null | head -3` # Task Write unit tests for the target file. Rules: 1. Detect the test framework from package.json / pyproject.toml / go.mod. If more than one is available (e.g., both jest and vitest), match whatever the nearby test files use. 2. Match the team's existing test style from the nearby test files (test naming, describe/it vs test, assertion style, mock style, setup/teardown patterns). 3. Colocate correctly: mirror wherever the nearby test files live — adjacent .test.ts, __tests__ subfolder, or dedicated tests/ directory. 4. Cover the public surface of the target: happy path, edge cases, error paths, boundary conditions. Skip private implementation details. 5. Do NOT over-mock. Mock external I/O (network, filesystem when not the point, time, randomness). Everything else uses real implementations. Over-mocked tests are worse than no tests. 6. If $ARGUMENTS includes a focus (e.g., "edge cases", "error handling"), weight the test suite toward that focus. 7. Use existing test utilities where they exist — check for local helpers, fixtures, or factories before creating new ones. 8. Show the diff, don't dump the whole file. Before writing tests, state the detected framework and colocation choice in one line so the user can veto if wrong.
Install

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).

Usage

How to use it

The command takes a file path plus optional focus. Framework detection and colocation happen automatically.

/test-unit src/utils/date-format.ts
Base case. Command reads package.json (detects Vitest or Jest), reads a nearby existing test file (matches style), writes src/utils/date-format.test.ts covering the public API. Total elapsed: ~15 seconds.
/test-unit src/services/user-service.ts edge cases
The focus argument nudges the test suite. Rather than balanced coverage of happy + edge + error paths, the resulting suite skews heavily toward boundary conditions and unusual inputs.
/test-unit lib/parser.py error handling
Same command, different language. Detects pytest via pyproject.toml, matches the project's assert-vs-self.assert style, colocates the test file per project convention.
/test-unit src/api/routes/users.ts happy path only
Use focus to constrain scope. Useful when a file is large and you want a starter test suite you'll flesh out later — get the happy path covered first, add edge cases in follow-up invocations.
/test-unit internal/auth/session.go
Go project. Command detects 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.
Variations

Alternative configs

Adjust for framework-specific preferences or team-specific patterns.

Framework-specific override
If your project has multiple test frameworks installed
Add rule: "For files under 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.
Coverage-target variant
For teams enforcing minimum coverage
Append rule: "After writing tests, run 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.
Strict mock-boundary variant
For teams that want zero mocking of internal code
Change rule 5 to: "Only mock at the boundary of your process: network (fetch, http clients), filesystem when the file content isn't the point of the test, the clock, and randomness. NEVER mock functions defined in your own codebase — refactor for testability instead." Enforces the higher-quality testing discipline.
Troubleshooting

Common issues and fixes

The five issues teams hit most often with /test-unit.

Command uses the wrong test framework
Multiple frameworks are installed and the nearby-test-file heuristic picked the wrong one, or the project uses a framework that isn't in package.json (e.g., a monorepo-level dev dep).
Add an explicit rule to the command: "This project uses . Do not use even if it appears installed." Explicit rules beat auto-detection when your project has framework ambiguity.
Tests land in the wrong location (colocation vs __tests__ folder)
The nearby-test-file search didn't find a nearby example, or the target file is in a directory with no existing test convention.
Add explicit colocation rule: "Always colocate test files adjacent to source, using .test.<ext> suffix. Do NOT create __tests__ subfolders." Or the opposite — whichever your project uses. Explicit beats inferred.
Generated tests are heavy on mocks
Base testing culture is skewed toward heavy mocking, or the target file has many dependencies that look like external I/O but aren't.
Strengthen rule 5. Change to: "NEVER mock internal modules. If a dependency isn't obviously external I/O (fetch, fs, Date.now, Math.random, subprocess), use the real implementation. Refactor for testability if the real dependency is hard to use."
Command tries to write to a location outside the allowed paths
Your project's test convention isn't captured in the allowed-tools Write patterns.
Add your pattern to the 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.
Tests generated but tests fail immediately on run
The command wrote tests based on inferred behavior, not verified behavior — imports wrong, function signature guessed.
Add rule: "Before writing tests, read the target file and the closest existing test file in its directory. Confirm the actual exports, imports, and function signatures before writing test code." Explicit read-first prevents inferred-import mistakes.

🐛 Hit an error?

AI Error Hub covers Claude Code errors and stack traces — cross-referenced with everything on this site.

Visit AI Error Hub →
FAQ

Frequently asked questions

The questions developers ask most about /test-unit.

Four differences:

  1. Framework detection reads real package.json (not guessed from imports).
  2. Style-matching reads a nearby existing test file (not invented).
  3. Colocation follows what the project already does (not arbitrary).
  4. 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, Glob
  • Write(**/*.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-check as 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.

Share with