Working with Tests
Tests are the safety net that keeps your code working as expected. But writing tests can feel like a chore -- especially when you already know the code works. Claude Code changes that dynamic by generating meaningful tests, running your test suite, interpreting failures, and helping you adopt test-driven development practices.
In this lesson, you will learn how to use Claude Code to write tests, run them, fix failures, and build a solid testing workflow.
Generating Tests from Existing Code
The most common testing workflow with Claude Code is asking it to write tests for code that already exists. Claude Code reads your implementation, understands what it does, and generates tests that cover the important cases.
Basic Test Generation
> Write tests for src/utils/formatPrice.ts
Claude Code reads the file and generates tests:
Claude Code: [Reads src/utils/formatPrice.ts]
This function formats a number as a price string with currency symbol
and two decimal places. I'll write tests covering normal cases,
edge cases, and error handling.
[Creates src/__tests__/utils/formatPrice.test.ts]
The generated test file might look like this:
import { formatPrice } from '@/utils/formatPrice';
describe('formatPrice', () => {
it('formats a standard price', () => {
expect(formatPrice(29.99)).toBe('$29.99');
});
it('formats a whole number with two decimal places', () => {
expect(formatPrice(100)).toBe('$100.00');
});
it('formats zero', () => {
expect(formatPrice(0)).toBe('$0.00');
});
it('formats a price with one decimal place', () => {
expect(formatPrice(9.5)).toBe('$9.50');
});
it('formats large numbers with commas', () => {
expect(formatPrice(1234567.89)).toBe('$1,234,567.89');
});
it('handles negative prices', () => {
expect(formatPrice(-15.99)).toBe('-$15.99');
});
it('rounds to two decimal places', () => {
expect(formatPrice(10.999)).toBe('$11.00');
});
it('throws for non-numeric input', () => {
expect(() => formatPrice(NaN)).toThrow('Invalid price');
});
});
Notice how Claude Code does not just test the "happy path." It includes edge cases like zero, negative numbers, rounding, and invalid input.
Testing a React Component
> Write tests for the LoginForm component at
src/components/LoginForm.tsx
Claude Code reads the component and writes tests using your testing library (React Testing Library, for example):
Claude Code: [Reads src/components/LoginForm.tsx]
[Reads package.json to check testing dependencies]
I see you're using React Testing Library with Jest. I'll write tests
covering rendering, user interaction, validation, and form submission.
[Creates src/__tests__/components/LoginForm.test.tsx]
The tests would cover:
- The form renders with email and password fields
- Submitting with empty fields shows validation errors
- Submitting with valid data calls the onSubmit handler
- The submit button shows a loading state during submission
- Error messages display when the API returns an error
Testing an API Route
> Write tests for the POST /api/orders endpoint in
src/app/api/orders/route.ts
Claude Code reads the route handler and generates tests that mock dependencies (database, external APIs) and test the request/response cycle:
Claude Code: [Reads src/app/api/orders/route.ts]
[Reads src/lib/db.ts to understand the database interface]
I'll write tests covering valid orders, validation errors,
authentication, and database error handling.
[Creates src/__tests__/api/orders.test.ts]
Test Frameworks: Claude Code Adapts
Claude Code works with whatever test framework your project uses. It detects the framework from your configuration and dependencies:
| Framework | Detection | Language |
|---|---|---|
| Jest | jest.config.* or jest in package.json | JavaScript/TypeScript |
| Vitest | vitest.config.* or vitest in package.json | JavaScript/TypeScript |
| Pytest | pytest.ini, conftest.py, or pytest in requirements | Python |
| Go test | *_test.go files | Go |
| RSpec | spec/ directory, Gemfile with rspec | Ruby |
| JUnit | pom.xml or build.gradle with JUnit | Java |
You do not need to tell Claude Code which framework to use. It reads your project configuration and generates tests in the correct format.
# Claude Code detects Vitest and generates accordingly
> Write tests for the auth module
Claude Code: [Reads vitest.config.ts]
I see you're using Vitest. I'll generate tests using Vitest syntax.
[Creates src/__tests__/auth.test.ts]
Uses: describe, it, expect, vi.fn(), vi.mock()
# In a Python project, Claude Code uses Pytest
> Write tests for the data processing module
Claude Code: [Reads pytest.ini]
I'll generate Pytest tests.
[Creates tests/test_data_processing.py]
Uses: def test_*, assert, pytest.fixture, pytest.raises
Running Tests
Claude Code can run your test suite and interpret the results. This is powerful because it closes the loop between writing code and verifying it works.
Running the Full Suite
> Run the tests
Claude Code executes the appropriate command based on your project:
Claude Code: [Runs: npm test]
Test Suites: 14 passed, 2 failed, 16 total
Tests: 47 passed, 3 failed, 50 total
Failed tests:
FAIL src/__tests__/utils/dates.test.ts
- formats relative time correctly
FAIL src/__tests__/api/users.test.ts
- returns 401 for unauthenticated requests
- updates user profile
Would you like me to investigate and fix the failures?
Running Specific Tests
> Run just the authentication tests
> Run the test file at src/__tests__/api/orders.test.ts
> Run only the tests that match "payment"
Claude Code translates these to the right commands:
# For Jest
[Runs: npx jest --testPathPattern="auth"]
[Runs: npx jest src/__tests__/api/orders.test.ts]
[Runs: npx jest -t "payment"]
# For Vitest
[Runs: npx vitest run auth]
[Runs: npx vitest run src/__tests__/api/orders.test.ts]
[Runs: npx vitest run -t "payment"]
# For Pytest
[Runs: pytest tests/test_auth.py]
[Runs: pytest -k "payment"]
Test-Driven Development (TDD) with Claude Code
TDD flips the usual workflow: you write the test first, then implement the code to make it pass. Claude Code is a great TDD partner.
The TDD Workflow
Step 1: Describe what you want to build
> I want to create a function called calculateDiscount that takes
a price and a discount percentage, and returns the discounted price.
Write the tests first. It should:
- Return the correct discounted price
- Handle 0% and 100% discounts
- Throw an error if the discount is negative or over 100
- Round to 2 decimal places
Claude Code writes the tests:
Claude Code: I'll write the tests first, then we can implement the
function to make them pass.
[Creates src/__tests__/utils/calculateDiscount.test.ts]
Step 2: Run the tests (they should all fail)
> Run the new tests to confirm they fail
Claude Code: [Runs: npx jest calculateDiscount]
All 6 tests failed as expected (module not found - the function
doesn't exist yet).
Step 3: Ask Claude Code to implement
> Now implement calculateDiscount in src/utils/calculateDiscount.ts
to make all the tests pass
Claude Code: [Reads the test file to understand the expected behavior]
[Creates src/utils/calculateDiscount.ts]
[Runs: npx jest calculateDiscount]
All 6 tests passing.
Step 4: Refactor if needed
> The implementation works but can you make it more readable?
Keep the tests passing.
This is the classic Red-Green-Refactor cycle of TDD, with Claude Code handling the implementation.
TDD for Features
You can also use TDD for larger features:
> I want to add a coupon code system. Before writing any implementation,
write tests for a CouponService that should:
- Validate coupon codes against the database
- Calculate the discount for a given coupon and order total
- Handle expired coupons
- Handle usage limits (each coupon can only be used N times)
- Return appropriate errors for invalid coupons
After Claude Code writes comprehensive tests, you can implement the service step by step, running the tests after each piece to track your progress.
Fixing Failing Tests
When tests fail, Claude Code can determine whether the bug is in the implementation or the test itself.
Bug in the Implementation
> The test "creates order with valid data" is failing with:
Expected: { status: "pending", total: 29.99 }
Received: { status: "pending", total: 0 }
Claude Code reads both the test and the implementation:
Claude Code: [Reads src/__tests__/api/orders.test.ts]
[Reads src/lib/orders.ts]
The test is correct -- the total should be 29.99. The bug is in the
createOrder function: it calculates the total before the cart items
are loaded from the database, so it always gets 0.
[Edits src/lib/orders.ts]
Moved the total calculation after the await for cart items
Bug in the Test
Sometimes the test itself is wrong:
> This test started failing after we updated the API response format:
Expected: { user: { name: "John" } }
Received: { data: { user: { name: "John" } } }
Claude Code: The API response format was updated to wrap responses in a
data field, but the test wasn't updated to match.
[Edits src/__tests__/api/users.test.ts]
Updated: expect(response.body.user.name) → expect(response.body.data.user.name)
Updated 5 similar assertions in the same test file
Bulk Fixing After a Refactor
After a significant refactoring, many tests might break. Claude Code can fix them in batch:
> I renamed the User type to UserProfile and updated the source code,
but 15 tests are failing because they still reference the old type.
Fix all the test files.
Claude Code searches for all test files using the old name and updates them systematically.
Adding Tests for Edge Cases
Your existing tests might cover the happy path but miss edge cases. Claude Code can identify and fill those gaps:
> Look at the tests for src/lib/payment.ts and add tests for
edge cases that aren't covered yet
Claude Code reads the implementation and the existing tests, then identifies what is missing:
Claude Code: [Reads src/lib/payment.ts]
[Reads src/__tests__/lib/payment.test.ts]
The existing tests cover successful payments and basic validation.
Here are the edge cases I'll add:
- Payment with amount of exactly $0.00 (free order)
- Payment with very large amount ($999,999.99)
- Network timeout during Stripe API call
- Duplicate payment attempt (idempotency)
- Currency conversion edge cases (3-decimal currencies like KWD)
- Race condition: two payments for the same order simultaneously
[Edits src/__tests__/lib/payment.test.ts]
Added 6 new test cases
Coverage-Guided Testing
If you use a coverage tool, share the report with Claude Code:
> Here's the coverage report for src/lib/auth.ts:
Lines: 75% (missing lines 45-52, 78-85, 102-110)
Branches: 60%
Add tests to improve coverage for the uncovered lines
Claude Code reads those specific line ranges, understands what logic they contain, and writes tests that exercise those paths.
Integration Tests
Claude Code can write tests that verify how different parts of your system work together.
> Write an integration test for the user registration flow:
1. POST to /api/auth/register with valid data
2. Verify the user is created in the database
3. Verify a welcome email is queued
4. Verify the response includes a valid JWT token
Claude Code creates an integration test that sets up the necessary environment, runs through the flow, and verifies each step:
Claude Code: [Reads the registration endpoint and related modules]
[Creates src/__tests__/integration/registration.test.ts]
- Sets up a test database with clean state
- Mocks the email service to capture sent emails
- Makes a real HTTP request to the registration endpoint
- Verifies the database record was created with correct fields
- Verifies the welcome email was queued with the right content
- Verifies the JWT token decodes to the correct user ID
- Cleans up after each test
End-to-End Test Setup
> Write a Playwright test that verifies the login flow:
navigate to /login, enter credentials, click submit, and
verify redirect to /dashboard
Claude Code generates browser-based tests:
import { test, expect } from '@playwright/test';
test('user can log in and reach dashboard', async ({ page }) => {
await page.goto('/login');
await page.fill('input[name="email"]', 'test@example.com');
await page.fill('input[name="password"]', 'password123');
await page.click('button[type="submit"]');
await expect(page).toHaveURL('/dashboard');
await expect(page.locator('h1')).toContainText('Dashboard');
});
Best Practices: Reviewing Generated Tests
Claude Code generates good tests, but you should always review them. Here is what to look for:
Are the Assertions Meaningful?
Watch out for tests that pass but do not actually verify anything useful:
# Weak assertion - this passes even if the function returns garbage
expect(result).toBeDefined();
# Strong assertion - verifies the actual value
expect(result).toBe('$29.99');
If you see weak assertions, ask Claude Code to strengthen them:
> Some of these tests just check that the result is defined.
Make the assertions more specific.
Are Edge Cases Covered?
Check whether the tests cover:
- Empty inputs
- Null or undefined values
- Boundary values (0, -1, MAX_INT)
- Invalid types
- Concurrent access
- Error conditions
Are Mocks Realistic?
Mocks should behave like the real thing. If Claude Code mocks a database call, verify the mock returns data in the same shape as the real database.
> The database mock in the user tests returns a plain object, but the
real database returns a Row instance with a toJSON() method. Can you
update the mock to be more realistic?
Do Tests Run Independently?
Each test should be able to run on its own without depending on the order of other tests. Watch out for shared state between tests.
> These tests seem to depend on each other -- the second test relies
on data created by the first test. Can you make them independent?
A Complete Testing Workflow
Here is how a full testing workflow with Claude Code might look for a new feature:
# 1. Write the feature
> Add a password reset feature with a POST /api/auth/reset endpoint
# 2. Generate tests
> Write comprehensive tests for the password reset endpoint
# 3. Run the tests
> Run the new tests
# 4. Fix any failures
> The test for expired tokens is failing. Fix it.
# 5. Add edge case tests
> Add tests for these edge cases:
- Token that has already been used
- Password that doesn't meet the strength requirements
- User account that has been deactivated
# 6. Run full suite to check for regressions
> Run the complete test suite to make sure nothing else broke
# 7. Check coverage
> What's the test coverage for the password reset module?
This workflow ensures your new feature is well-tested from the start.
What You Learned
In this lesson, you learned how to use Claude Code for testing:
- Generate tests from existing code with automatic edge case coverage
- Claude Code adapts to your test framework (Jest, Vitest, Pytest, and others)
- Run tests and interpret results directly in the Claude Code conversation
- Use TDD by writing tests first, then asking Claude Code to implement the code
- Fix failing tests with Claude Code determining whether the bug is in the test or implementation
- Add edge case tests to improve coverage of existing test suites
- Write integration and end-to-end tests for full-flow verification
- Review generated tests for meaningful assertions, realistic mocks, and test independence
In the next module, you will explore advanced features of Claude Code including CLAUDE.md files, custom slash commands, MCP servers, and hooks.
Questionário
Discussion
Sign in to join the discussion.

