Course navigation
Claude CodeLesson 10 of 25

Writing & Running Tests

Testing is not just something you add at the end — it is a workflow. Claude Code can write tests before the code exists, run your entire suite, diagnose failures, and fix the code or the test.

Two modes: generate vs TDD

You can use Claude Code for testing in two distinct ways. Use both — they serve different moments in the build cycle.

ModeWhen to useExample prompt
Generate tests for existing codeYou have a function or module already written. Ask Claude to cover it with tests.“Write tests for @src/lib/format.ts — happy path, null input, empty string.”
TDD — test first, code secondDescribe what you want to build. Claude writes failing tests first, then implements.“Write failing tests for a formatCurrency(amount, currency) function, then implement it.”

The TDD loop

Test-driven development follows a tight five-step cycle. Claude Code speeds up every step.

  1. Write the test
  2. Run — it fails (this proves the test checks something real)
  3. Write the code
  4. Run — it passes
  5. Refactor and repeat
The red step is intentional. A test that fails before you write the code proves the test is actually checking something real — not just always passing.

Example A — generate tests for existing code

You have a formatCurrency utility already written. Ask Claude to write full coverage.

src/lib/format.ts
export function formatCurrency(amount: number, currency = 'USD'): string {
if (isNaN(amount)) throw new Error('Invalid amount');
return new Intl.NumberFormat('en-US', { style: 'currency', currency }).format(amount);
}
Y

Write Vitest tests for @src/lib/format.ts. Cover: happy path (positive number), zero, negative number, a non-USD currency, and the NaN error case. Put the tests in src/__tests__/format.test.ts.

C

I'll write 5 tests covering all those cases. Each test will import formatCurrency directly and use expect() assertions.

Vitest — format.test.ts
formats positive USD amount (3ms)
formats zero as $0.00 (1ms)
formats negative amount (1ms)
formats EUR currency (2ms)
throws on NaN input (1ms)
5 passed
Always name the cases explicitly in your prompt (“zero, negative, non-USD, NaN”) rather than asking for “full coverage.” Claude produces more targeted, readable tests when you name the scenarios.

Example B — TDD from scratch

You want a slugify utility but have not written it yet. Use TDD: tests first, implementation second.

1

Write failing tests

Y

Write failing Vitest tests for a slugify(text: string): string function that does not exist yet. It should: lowercase the text, replace spaces with hyphens, strip special characters, and trim leading/trailing hyphens. Save to src/__tests__/slugify.test.ts. Do NOT create the implementation yet.

C

I'll write the tests first — they will all fail since slugify doesn't exist. This confirms the tests are actually testing something.

Vitest — slugify.test.ts (before implementation)
lowercases text
ReferenceError: slugify is not defined
replaces spaces with hyphens
ReferenceError: slugify is not defined
strips special characters
ReferenceError: slugify is not defined
trims leading/trailing hyphens
ReferenceError: slugify is not defined
4 failed0 passed

4 failures — expected. The function does not exist yet.

2

Implement to pass

Y

Now create src/lib/slugify.ts — implement slugify() so that all tests in @src/__tests__/slugify.test.ts pass. Do not change the tests.

C

I'll implement slugify in src/lib/slugify.ts to satisfy every test case without touching the test file.

src/lib/slugify.ts
export function slugify(text: string): string {
return text.toLowerCase().trim()
.replace(/[^\w\s-]/g, '')
.replace(/[\s_-]+/g, '-')
.replace(/^-+|-+$/g, '');
}
Vitest — slugify.test.ts (after implementation)
lowercases text (2ms)
replaces spaces with hyphens (1ms)
strips special characters (1ms)
trims leading/trailing hyphens (1ms)
4 passed
Key rule: always tell Claude “do not change the tests” when asking it to implement the function. Otherwise it may weaken assertions instead of writing correct code.

Running the full suite

Claude Code can run your entire test suite and report results back. Ask it from inside the Claude session — or run tests yourself in the integrated terminal.

Y

Run the full test suite and tell me which tests are failing.

C

Running npm run test now. I'll report the results and highlight any failures.

Claude Code — full suite run
# Claude runs: npm run test
src/__tests__/format.test.ts
5 tests passed
src/__tests__/slugify.test.ts
4 tests passed
src/__tests__/tasks.test.ts
2 tests passed
11 tests passed — 0 failed

For continuous feedback while you work, run Vitest in watch mode in a separate terminal:

Terminal — watch mode
$ npx vitest --watch
Watching for file changes… (press q to quit)
Re-running tests for: src/lib/slugify.ts
4 tests passed

Fixing a failing test

A test breaks after a code change. Paste the failure into Claude and it will diagnose whether the code is wrong or the test needs updating.

Terminal — unexpected failure
formatCurrency — formats zero as $0.00
AssertionError: expected '$0.00' to equal '0'
! 1 test failed — 10 passed
Y

Test failing after my last change to @src/lib/format.ts: AssertionError: expected '$0.00' to equal '0'. The test expects '$0.00' but the function now returns '0'. Which is wrong — the test or the code?

C

The code is wrong. Zero is a valid currency amount and should be formatted as '$0.00'. Your recent change removed the Intl.NumberFormat call for zero values. I'll fix format.ts — the test should not change.

Golden rule: when a test breaks after a code change, the code is almost always the problem — not the test. Only change a test if the requirements genuinely changed. Ask Claude to confirm which is wrong before letting it touch the test file.

Prompting patterns that work

GoalPrompt pattern
Cover an existing function"Write Vitest tests for @src/lib/x.ts — cover happy path, [list edge cases]."
TDD — tests first"Write failing tests for [describe function]. Do NOT implement it yet."
TDD — implement"Implement [function] so all tests in @src/__tests__/x.test.ts pass. Do not change the tests."
Fix a failure"Test failing: [paste error]. File: @src/lib/x.ts. Is the code or the test wrong? Fix accordingly."
Run the suite"Run npm run test and report which tests are failing and why."
Add a missing case"Add a test to @src/__tests__/x.test.ts for the case where [describe scenario]."

Works with any test framework

Claude Code adapts to whatever framework your project already uses. Name it in the prompt or rely on CLAUDE.md to carry the convention.

FrameworkTypical stack
VitestTypeScript / Vite
JestJavaScript / Node
PytestPython
Go testGo
Add your test framework to CLAUDE.md once: Tests: Vitest, colocated in src/__tests__/. Every session knows which framework and file location to use without being told.

Quick reference

PracticeOne-line rule
Name test cases explicitlyhappy path, zero, null, error — not just "full coverage"
@-mention the file under testClaude reads the source before writing tests
Keep framework in CLAUDE.mdRight framework and path every session
TDD: test first, implement secondConfirm tests fail before implementing
Do not change tests to make them passAdd "do not change the tests" to implement prompts
Paste the exact error when fixingFull error text speeds up diagnosis
Run watch mode while buildingnpx vitest --watch gives instant feedback on every save

Before you continue

  • Generate tests for existing code, or use TDD — tests first, implementation second.
  • Name test cases explicitly and always @-mention the source file.
  • Tell Claude “do not change the tests” when implementing to pass them.
  • Paste the full error message when a test fails after a code change.
  • Next: best practices that make Claude Code more effective every day.

What's Next

Your test coverage is growing. The final Claude Code lesson distils the best practices — habits and patterns that make Claude Code more effective every day.