Claude Code TDD: AI-Assisted Test-Driven Dev Guide
Learn how to implement TDD with Claude Code. Includes complete Red-Green-Refactor process, Hooks automation configuration, and CLAUDE.md setup examples.
TDD (Test-Driven Development) is one of Anthropic’s officially recommended Claude Code workflows. This article teaches you how to make Claude automatically follow the Red-Green-Refactor cycle to write higher quality code.
Why TDD + Claude Code?
Claude Code excels at tasks with clear targets. Tests are the best targets:
Traditional: Write code → Manual test → Find bugs → Fix → Repeat
TDD: Write test → Test fails (Red) → Write code to pass (Green) → Refactor
Advantages of Claude + TDD:
| Advantage | Description |
|---|---|
| Clear success criteria | Tests pass = Task complete |
| Auto-iteration | Claude can run tests, see results, fix issues |
| Less back-and-forth | No manual checking “did it work?” |
| Code quality | Naturally has test coverage |
TDD Basics: Red-Green-Refactor
Three Phases
┌─────────────────────────────────────────────────────────┐
│ │
│ 🔴 RED 🟢 GREEN 🔄 REFACTOR │
│ │
│ Write failing Write minimal Improve code │
│ test code to pass keep tests passing │
│ │
│ │ │ │ │
│ ▼ ▼ ▼ │
│ Test must fail Test must pass Tests still pass │
│ │
└─────────────────────────────────────────────────────────┘
Practical Example
Step 1: Red
You: Write a test that verifies add(2, 3) returns 5
Claude:
// add.test.js
test('add(2, 3) should return 5', () => {
expect(add(2, 3)).toBe(5);
});
// Run tests
npm test
// ❌ FAIL - add is not defined
Step 2: Green
You: Make the test pass
Claude:
// add.js
function add(a, b) {
return a + b;
}
// Run tests
npm test
// ✅ PASS
Step 3: Refactor
You: Refactor the code, add type checking
Claude:
// add.ts
function add(a: number, b: number): number {
return a + b;
}
// Run tests
npm test
// ✅ PASS (tests still pass)
CLAUDE.md Configuration
Basic TDD Rules
Add to CLAUDE.md in project root:
# TDD Development Rules
## Core Principles
1. **Tests first**: Any new feature must have failing tests first
2. **Minimal implementation**: Only write code just enough to pass tests
3. **Continuous refactoring**: Consider refactoring after each green light
## Development Flow
1. Receive requirement → Write tests first
2. Confirm test fails (Red)
3. Write minimal code to pass tests (Green)
4. Refactor (keep Green)
5. Repeat
## Forbidden
- ❌ Don't write feature code without tests
- ❌ Don't delete or skip existing tests
- ❌ Don't write "tests later" code
Hooks Automation Configuration
Auto-Run Tests
Auto-run tests after every code modification:
{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm test -- --passWithNoTests --watchAll=false 2>/dev/null",
"onFailure": "warn"
}
]
}
]
}
}
Force TDD: Tests Must Pass to Complete
{
"hooks": {
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "command",
"command": "npm test -- --passWithNoTests",
"onFailure": "block"
}
]
}
]
}
}
Complete TDD Hook Configuration
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "prompt",
"prompt": "Check if writing feature code. If yes, confirm corresponding test file exists. If test doesn't exist, remind to write tests first. Return 'approve' or reminder message."
}
]
}
],
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm test -- --passWithNoTests --watchAll=false",
"onFailure": "warn"
}
]
}
],
"Stop": [
{
"matcher": "*",
"hooks": [
{
"type": "prompt",
"prompt": "Check: 1) Was feature code added or modified? 2) If yes, do corresponding tests exist and pass? If tests don't exist or fail, return 'block' with explanation. Otherwise return 'approve'."
},
{
"type": "command",
"command": "npm test",
"onFailure": "block"
}
]
}
]
}
}
Practical Example: Todo App
Let’s implement a simple Todo feature using TDD.
Step 1: Define Requirements
Requirements: Implement TodoList class
- addTodo(text): Add todo item
- getTodos(): Get all todos
- completeTodo(id): Mark as complete
- getCompletedCount(): Get completed count
Step 2: First Test (Red)
You: Implement TodoList using TDD. Write the first test: add todo item.
Claude generates:
// todoList.test.js
const { TodoList } = require('./todoList');
describe('TodoList', () => {
describe('addTodo', () => {
test('should add a todo item', () => {
// Arrange
const todoList = new TodoList();
// Act
todoList.addTodo('Buy milk');
// Assert
const todos = todoList.getTodos();
expect(todos).toHaveLength(1);
expect(todos[0].text).toBe('Buy milk');
});
});
});
Run tests:
npm test
# ❌ FAIL - Cannot find module './todoList'
Step 3: Minimal Implementation (Green)
You: Make the test pass, write minimal code.
Claude generates:
// todoList.js
class TodoList {
constructor() {
this.todos = [];
}
addTodo(text) {
this.todos.push({ text });
}
getTodos() {
return this.todos;
}
}
module.exports = { TodoList };
Run tests:
npm test
# ✅ PASS
Step 4: Next Test
You: Continue TDD, add test for completeTodo feature.
// todoList.test.js (add)
describe('completeTodo', () => {
test('should mark a todo as completed', () => {
// Arrange
const todoList = new TodoList();
todoList.addTodo('Buy milk');
const todoId = todoList.getTodos()[0].id;
// Act
todoList.completeTodo(todoId);
// Assert
const todo = todoList.getTodos()[0];
expect(todo.completed).toBe(true);
});
});
npm test
# ❌ FAIL - todo.id is undefined
Step 5: Expand Implementation
You: Make the test pass.
// todoList.js (updated)
class TodoList {
constructor() {
this.todos = [];
this.nextId = 1;
}
addTodo(text) {
this.todos.push({
id: this.nextId++,
text,
completed: false
});
}
getTodos() {
return this.todos;
}
completeTodo(id) {
const todo = this.todos.find(t => t.id === id);
if (todo) {
todo.completed = true;
}
}
}
module.exports = { TodoList };
npm test
# ✅ PASS (2 tests)
TDD FAQ
Q: When is TDD not suitable?
A:
- Exploratory prototypes (unsure what to build)
- UI style adjustments
- Configuration file changes
In these cases, write code first, add tests later.
Q: How detailed should tests be?
A: Follow the testing pyramid:
/\
/ \ E2E (few)
/----\
/ \ Integration (medium)
/--------\
/ \ Unit tests (many)
Q: Claude skips tests and writes code directly?
A: Be explicit in the prompt:
You: Implement this feature using TDD.
Step 1: Write failing tests first
Step 2: After I confirm tests fail, then write code
Or use Hooks to enforce:
{
"hooks": {
"PreToolUse": [
{
"matcher": "Write",
"hooks": [
{
"type": "prompt",
"prompt": "If writing feature code (not tests), check if test file exists. If not, return 'block' and remind to write tests first."
}
]
}
]
}
}
Best Practices Summary
1. Prompt Template
Implement [feature description] using TDD:
1. Write failing tests first
2. After confirming tests fail, write minimal code to pass
3. After all tests pass, consider refactoring
2. Checklist
For each new feature:
- Write tests first
- Confirm tests fail (Red)
- Write code to pass tests (Green)
- Refactor (keep Green)
- Check coverage
3. Recommended Configuration Combo
| Tool | Purpose |
|---|---|
| CLAUDE.md | Define TDD rules |
| Hooks | Auto-run tests |
| Stop Hook | Force tests to pass |
Next Steps
After mastering TDD workflow, recommended reading:
- Hooks Complete Tutorial - More automation configurations
- Custom Agents - Create dedicated testing agents
- Director Mode - Advanced development mode
Resources
Last updated: 2026-01-19