headroom.walls.sh · test

Writing Tests with Claude Code

Claude Code can write tests, run them, read the failures, fix the code, and repeat — unattended. The test-fix loop is one of its strongest workflows. This page covers how to drive TDD, add coverage to existing code, and keep long test sessions from quietly draining your 5-hour window.

The test-fix loop — Claude Code's core workflow

Give Claude Code a failing test (or ask it to write one), and it will enter a loop: write → run → read output → fix → run again. This is the workflow it was built for.

claude "write tests for src/auth.js, run them, and fix any failures"

What happens:

  1. It reads src/auth.js and any existing test files
  2. Writes a new test file targeting the exported functions
  3. Runs the test suite
  4. Reads any failures and edits the source or test accordingly
  5. Reruns until green

The loop is automatic — you don't need to supervise each cycle. Start it, check back when it's done.

TDD: write the failing test first

Claude Code handles strict TDD well. Ask for the test before the implementation:

claude "I want to add a validateEmail function to src/utils.js.
Write a failing test for it first — it should validate format, reject TLDs shorter than 2 chars, and accept subdomains. Don't implement it yet."

Once you've reviewed the test spec:

claude "now implement validateEmail to make those tests pass"

This keeps the test as the contract. Claude Code reads the failing test to understand what's expected, then writes code to satisfy it exactly — not the other way around.

The "write test first, implement second" two-step works best as two separate commands — it forces Claude Code to treat the test as the spec rather than reverse-engineering the test from an implementation it already has in mind.

Adding tests to existing code

The most common request: a module with no tests. Give Claude Code the module and a coverage goal:

claude "add unit tests for src/parser.js — cover the happy path, malformed input, and edge cases like empty strings and null. Use the existing test framework in package.json."

It will:

If you want a specific coverage target:

claude "add tests for src/parser.js. Run coverage after — I want 80%+ line coverage."

Whole-module test generation

For a directory with no tests at all:

claude "write tests for everything in src/models/ — one test file per module, placed in test/models/. Match the naming convention test-{module}.js. Run the suite after writing."

Claude Code reads each file, infers the public API, writes tests, and runs the suite. Failures in one file don't stop it from writing the others — it logs failures and continues across the directory.

Framework-specific patterns

Jest / Vitest (JavaScript)

claude "add Jest tests for src/api/users.js — mock the database with jest.mock, test each exported function, run with npm test"

Claude Code understands jest.mock(), beforeEach/afterEach, snapshot testing, and async patterns. Ask it to match your project's existing test style and it will read the existing files before writing a single line.

pytest (Python)

claude "write pytest tests for app/services/auth.py — use fixtures, parametrize the edge cases, and run pytest -v after"

It understands fixtures, parametrize, monkeypatching, and the difference between unit and integration tests. Passing -v in the run command gives readable failure output Claude Code can act on.

Go testing

claude "add table-driven tests for pkg/parser/parser.go — follow the existing patterns in the repo, run go test ./pkg/parser/..."

Go's table-driven test pattern is idiomatic and Claude Code follows it correctly. Giving it the run command with the right package path avoids the "tests not found" confusion from running in the wrong directory.

Swift / XCTest

claude "write XCTest cases for Sources/Auth/TokenManager.swift — cover the happy path and the error cases, add them to the existing test target"

For Swift packages, Claude Code will add tests to the correct Tests/ directory and wire them into the package's test target — it reads the package manifest before writing.

Piping test output into Claude Code

If you're running tests manually and want Claude Code to fix the failures:

npm test 2>&1 | claude --print "read this test output and fix any failing tests"

The --print flag makes Claude Code non-interactive — it reads the piped output, fixes what it can, and exits. Useful for scripted pipelines, CI integration, and post-test hooks.

Or pass the output inline:

claude "here is my test output — find what is failing and fix it:

[paste failure output here]"

Keeping tests in sync during refactors

When you rename or restructure code that has tests, tell Claude Code about both:

claude "rename getUserById to fetchUser across the codebase. Update the implementation, all call sites, and all tests. Run the test suite after to confirm nothing broke."

The critical addition is "run the test suite after" — it verifies the rename was consistent rather than assuming the edits were correct.

Session budget warning: the test-fix loop is the fastest way to drain your 5-hour session window. Each iteration reads files, edits code, and runs the suite — typically 6–10 tool calls per cycle. Fifteen iterations of a complex test loop can consume 25–35% of your session budget without a visible pause. Check your usage before starting a long test run.

Know your headroom before a long test run

Headroom — live session usage in your menu bar

The test-fix loop burns session budget silently. Headroom shows your Claude Code session utilization (5h window) and weekly utilization (7d cap) live in the macOS menu bar — updated as the loop runs. No token, no API key: it reads the file Claude Code writes to ~/.claude/.

Install in one line:

brew install patwalls/tap/headroom

Color-coded from calm to amber to red as your window fills. Start a long test run when you're at 20% — not when you're at 70% and two cycles from a hard stop.


Debugging with Claude Code
Refactoring with Claude Code
Agent mode and subagents
5-hour session limit explained · 7-day weekly cap