DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Test-Driven Development with Claude Code: Write Tests First, Then Make Them Pass

Claude Code is well-suited for test-driven development (TDD). The key is knowing when to write tests first vs. when to generate code first. Here's how I use it for TDD.


The Core TDD Loop with Claude Code

Classic TDD: write a failing test → write the minimum code to pass → refactor.

With Claude Code, each phase looks like this:

Phase 1 — Write the test:

Write a test for a UserService.createUser() function.
Requirements:
- Takes email (string) and name (string)
- Returns the created user with an id field
- Throws ValidationError if email is invalid
- Throws DuplicateError if email already exists
- Does NOT test the database directly (use mocks)

Don't write the implementation yet.
Enter fullscreen mode Exit fullscreen mode

Phase 2 — Write the implementation:

The test is now written. Write the UserService.createUser()
implementation that makes all tests pass.
Here's the test: [paste the test]
Enter fullscreen mode Exit fullscreen mode

Phase 3 — Refactor:

The tests are passing. Refactor the implementation for
readability without changing behavior.
Enter fullscreen mode Exit fullscreen mode

Why "Write Tests First" Works Better Than "Generate Both"

Asking Claude Code to "write UserService with tests" in one shot often produces:

  • Tests that are written to match the implementation (not requirements)
  • Missing edge cases
  • Tests that pass because they test the wrong thing

Writing requirements → test → implementation forces:

  • Thinking through requirements before coding
  • Test cases that reflect actual expected behavior
  • Implementation that's shaped by testability

CLAUDE.md for TDD Projects

## Testing Rules
- Tests are written BEFORE implementations
- Test files are in `tests/` matching `src/` structure
- Test naming: describe("UserService", () => { describe("createUser", () => {
- Pattern: Arrange, Act, Assert (one assertion per it() block)
- External services are always mocked (no real API calls in tests)
- Database is mocked with vitest-mock-extended / pytest-mock
- Test data: use factories in `tests/factories/`, not raw objects

## TDD Workflow
1. Write failing test
2. Write minimum implementation to pass
3. Refactor (with tests still passing)
4. Run: npm test (must be 100% green before any PR)
Enter fullscreen mode Exit fullscreen mode

Generating Test Factories

Write a factory for the User model for use in tests.
Requirements:
- Default values for all required fields
- Override any field with an options object
- Returns typed User objects

User model:
[paste the type/model definition]
Enter fullscreen mode Exit fullscreen mode

Result:

// tests/factories/user.factory.ts
import type { User } from '@/types';

let counter = 0;

export function createUser(overrides: Partial<User> = {}): User {
  counter++;
  return {
    id: `user-${counter}`,
    email: `user${counter}@example.com`,
    name: `Test User ${counter}`,
    createdAt: new Date('2026-01-01'),
    ...overrides,
  };
}
Enter fullscreen mode Exit fullscreen mode

Generating Tests from Specifications

When you have a spec doc or user story, use it directly:

Here's the spec for the payment processing feature:
[paste the spec]

Write all tests that verify this spec. Focus on:
1. Happy path
2. Each error case mentioned in the spec
3. Edge cases not mentioned but implied by business rules
Enter fullscreen mode Exit fullscreen mode

Getting Better Test Coverage

For existing code without tests:

Analyze this function and generate tests that cover:
1. Normal inputs and expected outputs
2. Edge cases (null, empty, boundary values)
3. Error cases

Function: [paste the function]
Enter fullscreen mode Exit fullscreen mode

Then run coverage:

npm test -- --coverage
Enter fullscreen mode Exit fullscreen mode
Current coverage is 72%. The uncovered branches are:
[paste the coverage report]

Write tests to cover these branches.
Enter fullscreen mode Exit fullscreen mode

/test-gen Skill Workflow

With the /test-gen custom skill:

/test-gen src/services/user.service.ts
Enter fullscreen mode Exit fullscreen mode

This generates a test file at tests/services/user.service.test.ts with:

  • Import setup
  • Mock setup for dependencies
  • Test cases for each public method
  • Edge cases based on the implementation

Then you review, adjust requirements in the test, and run:

npm test tests/services/user.service.test.ts
Enter fullscreen mode Exit fullscreen mode

CI Integration

Add this to your GitHub Actions workflow so TDD is enforced:

- name: Run tests with coverage
  run: npm test -- --coverage --reporter=verbose

- name: Check coverage threshold
  run: npx vitest run --coverage.threshold.lines=80
Enter fullscreen mode Exit fullscreen mode

If coverage drops below 80%, the PR is blocked.


/test-gen skill is included in Code Review Pack (¥980) on PromptWorks.

👉 prompt-works.jp

Myouga (@myougatheaxo) — Security-focused Claude Code engineer.

Top comments (0)