Skip to main content

Test Automation

Claude Code removes the two biggest barriers to writing tests: getting started and repetitive work. Let's build a workflow that practices TDD (Test-Driven Development), raises test coverage, and prevents regression bugs.

TDD with Claude Codeโ€‹

The principle of TDD is simple: tests first, implementation second. Claude Code lets you spin this cycle very quickly.

The Red-Green-Refactor Cycleโ€‹

1. Red   โ€” Claude writes a failing test
2. Green โ€” Claude writes the minimal implementation that passes
3. Refactor โ€” Improve the code together with Claude

A real conversation example:

> Implement an email validation function using TDD.
Write the test cases first, then build an implementation that passes them.

Claude: I'll write the tests first.

// email.test.ts
describe('validateEmail', () => {
test('accepts valid email addresses', () => {
expect(validateEmail('user@example.com')).toBe(true);
expect(validateEmail('user+tag@domain.co.kr')).toBe(true);
});

test('rejects invalid formats', () => {
expect(validateEmail('notanemail')).toBe(false);
expect(validateEmail('@nodomain.com')).toBe(false);
expect(validateEmail('missing@')).toBe(false);
});

test('rejects empty strings', () => {
expect(validateEmail('')).toBe(false);
});
});

Now I'll write an implementation that passes these tests...

Adding Tests to Existing Codeโ€‹

When adding tests to code that's already written:

> Write unit tests for the calculateDiscount function in
src/services/order.service.ts.
Include edge cases (0% discount, 100% discount, stacked coupons).
> Write integration tests for the API endpoints we just implemented.
Use supertest, and cover both success and failure cases.

Raising Test Coverageโ€‹

> Run npm test -- --coverage and add tests to the files with
low coverage. Target at least 80%.
> Find all utility functions in the src/utils/ directory that
don't have a test file, and write tests for them.

Framework-Specific Patternsโ€‹

Jest (Node.js/TypeScript)โ€‹

> Write unit tests for UserService with Jest.
Mock external dependencies (DB, email service) with jest.mock.

The pattern Claude generates:

import { UserService } from '../services/user.service';
import { PrismaClient } from '@prisma/client';

jest.mock('@prisma/client');

describe('UserService', () => {
let userService: UserService;
let mockPrisma: jest.Mocked<PrismaClient>;

beforeEach(() => {
mockPrisma = new PrismaClient() as jest.Mocked<PrismaClient>;
userService = new UserService(mockPrisma);
});

describe('findById', () => {
it('returns an existing user', async () => {
const mockUser = { id: 1, email: 'test@example.com', name: 'John Doe' };
mockPrisma.user.findUnique = jest.fn().mockResolvedValue(mockUser);

const result = await userService.findById(1);

expect(result).toEqual(mockUser);
expect(mockPrisma.user.findUnique).toHaveBeenCalledWith({
where: { id: 1 }
});
});

it('returns null for a non-existent user', async () => {
mockPrisma.user.findUnique = jest.fn().mockResolvedValue(null);

const result = await userService.findById(999);

expect(result).toBeNull();
});
});
});

Vitest (Vite Ecosystem)โ€‹

> Write React component tests with Vitest.
Use @testing-library/react to simulate user interactions.

Pytest (Python)โ€‹

> Write tests for the FastAPI endpoints with pytest.
Use TestClient, and set up a test DB with a fixture.

Playwright (E2E)โ€‹

> Write an E2E test for the login flow with Playwright.
Cover successful login, wrong password, and account lockout scenarios.

Automatic Testing with Hooksโ€‹

Combined with the Hooks system, you can run tests automatically whenever a file is saved:

{
"hooks": {
"PostToolUse": [
{
"matcher": "Write|Edit",
"hooks": [
{
"type": "command",
"command": "npm test -- --passWithNoTests 2>&1 | tail -10"
}
]
}
]
}
}

Tests run automatically every time a file changes, and if they fail, Claude fixes the code immediately.

Automatically Fixing Failing Testsโ€‹

> Run npm test and fix all the failing tests.
Don't modify the tests themselves โ€” only fix the implementation code.
> Fix the tests that broke after the refactoring we just did.
First verify that each test checks the originally intended behavior,
and if the implementation is wrong, fix the implementation.

Managing Test Dataโ€‹

> Create factory functions for testing.
Make it easy to generate User, Order, and Product objects.
Use faker.js to generate realistic data.
// Example factory pattern Claude generates
import { faker } from '@faker-js/faker';

export const createUser = (overrides = {}) => ({
id: faker.number.int(),
email: faker.internet.email(),
name: faker.person.fullName(),
createdAt: faker.date.recent(),
...overrides
});

export const createOrder = (userId: number, overrides = {}) => ({
id: faker.number.int(),
userId,
total: faker.number.float({ min: 10, max: 1000, fractionDigits: 2 }),
status: 'pending',
...overrides
});

Test Strategy Guideโ€‹

Testing priorities when working with Claude Code:

  1. Business logic โ€” calculations, validation, state transitions
  2. API endpoints โ€” input/output contracts
  3. External service integrations โ€” isolation through mocking
  4. Edge cases โ€” empty values, maximum values, concurrency
> Prioritize what to test in this codebase.
Focus on areas with complex business logic and areas where bugs
occur frequently.

Things You Don't Need to Testโ€‹

  • Framework features themselves (Next.js routing, etc.)
  • Simple getters/setters
  • Trivial utilities of 3 lines or fewer

Test Automation Checklistโ€‹

  • Write tests alongside every new feature
  • Add a regression test with every bug fix
  • Set up Hooks to run tests automatically on file save
  • Integrate tests into the CI pipeline
  • Review coverage reports regularly