Claude Code Testing Guide: TDD, Mocking & Coverage
🩺 Summary
Most developers know they should write tests. They just don'...
📝 Details
Most developers know they should write tests. They just don't want to. Not because testing is useless—because writing tests manually is tedious. Mock data, assertions, coverage chasing—repetitive and mentally draining. Claude Code's most underrated skill isn't writing production code, it's **writing tests**. This guide skips the theory and goes straight to practice: TDD with Claude, auto-mocking, coverage optimization.
## 1. TDD Mode: Test First, Code Second
TDD: write a failing test first, then write the code to make it pass. Claude Code is naturally good at this because you don't need to switch context.
### 1.1 Ask Claude to Write Tests First
For an empty `calculator.py`:
```bash
claude "TDD this calculator module. Write pytest tests first, then implement.
Requirements:
- add(a, b) returns sum
- subtract(a, b) returns a - b
- multiply(a, b) returns product
- divide(a, b) returns quotient, ZeroDivisionError for 0
- Cover normal and edge cases"
```
Claude creates `test_calculator.py` first, then `calculator.py`. All tests pass.
**Comparison:**
| Step | Manual TDD | Claude Code TDD |
|:----|:-----------|:----------------|
| Write tests | 10 min | 30s (auto) |
| Implement | 15 min | 20s (auto) |
| Run tests | 5s | 5s |
| Refactor | 10 min | 30s (auto) |
| **Total** | **~35 min** | **~1.5 min** |
### 1.2 Backfill Tests for Existing Code
More common scenario: project is half-built, needs tests now.
```bash
claude -p "Write pytest tests for @models/user.py:
- Cover all public methods
- Mock database (no real DB)
- Normal and error flows
- Target 90%+ coverage"
```
Claude reads `models/user.py`, analyzes function signatures and branches, and generates the test file.
**Pro tip**: Reference files with `@`:
```bash
claude -p "Write unit tests for @models/user.py using pytest + unittest.mock"
```
## 2. Mocking: Don't Touch Real Dependencies
The hardest part of testing: the function calls a database, sends HTTP requests, or accesses the filesystem. Mocking replaces these with stand-ins.
### 2.1 Let Claude Auto-Generate Mocks
```bash
claude -p "Write tests for @services/payment.py:
- Mock PaymentGateway.send()
- Mock EmailService.notify()
- Verify welcome email on success
- Verify retry logic on failure
- Use pytest monkeypatch or unittest.mock"
```
Claude analyzes the service interfaces and generates correct mock code automatically.
### 2.2 Common Mock Templates
**HTTP Mock:**
```python
from unittest.mock import patch, MagicMock
def test_fetch_user_data():
mock_response = {"id": 1, "name": "Test", "email": "test@example.com"}
with patch("services.user.requests.get") as mock_get:
mock_get.return_value.status_code = 200
mock_get.return_value.json.return_value = mock_response
result = fetch_user_data(1)
assert result["name"] == "Test"
```
**Database Mock:**
```python
@pytest.fixture
def mock_db():
with patch("models.user.get_db_connection") as mock_conn:
cursor = MagicMock()
mock_conn.cursor.return_value = cursor
cursor.fetchone.return_value = {"id": 1, "name": "Alice"}
yield mock_conn
```
**Filesystem Mock:**
```python
from unittest.mock import mock_open, patch
def test_process_csv():
with patch("builtins.open", mock_open(read_data="name,age\nAlice,30")):
result = process_csv("data.csv")
assert len(result) == 1
```
### 2.3 Mock Best Practices
Add these to your CLAUDE.md:
```markdown
## Testing Conventions
- Use pytest + unittest.mock
- Mock external dependencies (DB, HTTP, filesystem)
- One test, one concern
- Name: test_<feature>_<scenario>
- Shared fixtures in conftest.py
```
## 3. Coverage Optimization
### 3.1 Scan Coverage Gaps
```bash
pytest --cov=./src --cov-report=json
claude "Analyze coverage.json. Find modules below 80%.
Suggest missing test scenarios for each."
```
### 3.2 Fill Gaps for Low-Coverage Files
```bash
claude -p "Read @src/utils/validator.py coverage report.
Add tests for uncovered branches:
- Each if/else path
- Exception handlers
- Edge cases (empty, None, extreme values)"
```
### 3.3 Phased Coverage Strategy
```bash
# Phase 1: Core logic → 80%
claude -p "Write tests for @src/core/*, target 80% coverage"
# Phase 2: Critical paths → 85%
claude -p "Write tests for @src/api/* handlers, target 85%"
# Phase 3: Config → 70%
claude -p "Write tests for @src/config/* parsers, target 70%"
```
## 4. Real Project Example
A `register.py` user registration module:
```python
import re
import requests
class UserRegistration:
def __init__(self, db_conn, email_service):
self.db = db_conn
self.email = email_service
def register(self, username, email, password):
if not re.match(r'^[a-zA-Z0-9_]{3,20}$', username):
raise ValueError("Invalid username")
if not re.match(r'^[^@]+@[^@]+\.[^@]+$', email):
raise ValueError("Invalid email")
if len(password) < 8:
raise ValueError("Password too short")
if self.db.query("SELECT id FROM users WHERE email=?", (email,)):
raise ValueError("Email already registered")
user_id = self.db.execute("INSERT INTO users ...", (username, email, password))
self.email.send_welcome(email, username)
return {"id": user_id, "username": username, "email": email}
```
```bash
claude -p "Write complete pytest tests for @register.py:
- Mock db and email_service
- Cover: success, invalid username, invalid email, short password, duplicate email
- Verify email.send_welcome called on success, not called on failure
- Hit 100% branch coverage"
```
Claude generates:
```python
@pytest.fixture
def registration():
db = MagicMock()
email = MagicMock()
return UserRegistration(db, email), db, email
def test_success(registration):
reg, db, email = registration
db.query.return_value = None
db.execute.return_value = 42
result = reg.register("testuser", "test@example.com", "password123")
assert result["id"] == 42
email.send_welcome.assert_called_once()
def test_invalid_username(registration):
reg, _, _ = registration
with pytest.raises(ValueError, match="Invalid username"):
reg.register("ab", "test@example.com", "password123")
```
## 5. Test Maintenance
### 5.1 Update Tests When Code Changes
```bash
# After changing code:
claude -p "I changed @services/payment.py process_refund signature
from (amount) to (amount, reason). Update the tests."
```
### 5.2 Bulk Test Updates
```bash
claude -p "Check all test files in @tests/ for collection errors
due to API changes. Update based on new @src/ signatures."
```
### 5.3 Review Tests Themselves
```bash
claude -p "Review @tests/test_payment.py:
- Missing scenarios?
- Mocks correct (args, return types)?
- Assertions strict enough?
- Unnecessary mocks?"
```
---
**Quick Reference:**
| Scenario | Command | Result |
|:---------|:--------|:-------|
| TDD new feature | `claude "TDD module.py"` | Tests + implementation |
| Backfill tests | `claude "Write tests for @file.py"` | Auto-generated tests |
| Mock deps | `claude "Mock DB and HTTP"` | Auto-mocked externals |
| Cover gaps | `claude "Analyze coverage, fill gaps"` | Targeted improvements |
| Update tests | `claude "Signature changed, update tests"` | Auto-adapted tests |
The biggest barrier to testing isn't technical—it's inertia. Claude Code reduces that barrier to zero. Just say "write tests" and it's done.
---
*More from the series:*
- [Hands-on: Build a Web App in 10 Minutes](/post/claude-code-todo-project-2026)
- [Advanced: CLAUDE.md & MCP Config](/post/claude-code-advanced-2026)
- [Pitfalls: 10 Common Mistakes](/post/claude-code-pitfalls-2026)
- [2026 AI Coding Tools Guide](/post/ai-coding-tools-guide-2026)
- [Team Collaboration Guide](/post/claude-code-team-collaboration-2026)
- [CI/CD Integration](/post/claude-code-ci-cd-2026)
- [Can Claude Code Be Used in Hong Kong?](/post/claude-code-xianggang-2026)
💬 Comments (0)