Why AI Changes Testing Forever
Testing has always been the activity most likely to be skipped under time pressure. Engineers know tests are important, but when the deadline looms, tests are the first thing cut. AI eliminates this tradeoff. When generating a comprehensive test suite takes minutes instead of hours, there is no excuse to skip testing.
But AI-generated tests need careful review. AI is excellent at test structure and coverage breadth, but it can assert the wrong behavior — testing that buggy code continues to be buggy. The AI-native testing workflow is: generate, review, correct, run, iterate.
What AI Does Well in Testing
- Test Structure: Creates well-organized describe/it blocks with proper setup/teardown
- Coverage Breadth: Thinks of edge cases you might miss (empty arrays, null values, boundary conditions)
- Boilerplate: Handles mocking, fixtures, and test utilities setup
- Pattern Matching: If you show it one test file, it replicates the pattern perfectly for others
The AI Testing Workflow
# Step 1: Generate tests
> Write comprehensive tests for app/lib/cart.ts. Include:
- All exported functions
- Edge cases: empty cart, single item, max quantity limits
- Price calculations with discounts and tax
- Cart persistence (add, remove, update quantity, clear)
Use our existing test patterns from app/lib/auth.test.ts
# Step 2: Review the generated tests critically
# Ask yourself for each assertion:
# "Is this testing the CORRECT behavior, or just the CURRENT behavior?"
# Step 3: Run the tests
> Run the tests you just wrote. Fix any failures that are bugs in
the tests (not bugs in the code being tested).
# Step 4: Check coverage
> Run coverage for cart.ts. Are there any untested code paths?
Add tests for any uncovered branches.
Unit Test Generation
// Source code: app/lib/pricing.ts
export function calculateDiscount(
subtotal: number,
couponCode: string | null,
isVIP: boolean
): { discount: number; finalPrice: number } {
let discount = 0;
// VIP gets 10% off
if (isVIP) {
discount += subtotal * 0.1;
}
// Coupon codes
if (couponCode === "SAVE20") {
discount += subtotal * 0.2;
} else if (couponCode === "FLAT10") {
discount += 10;
}
// Cap discount at 50% of subtotal
discount = Math.min(discount, subtotal * 0.5);
return {
discount: Math.round(discount * 100) / 100,
finalPrice: Math.round((subtotal - discount) * 100) / 100,
};
}
// AI-generated tests (after review and corrections):
import { describe, it, expect } from "vitest";
import { calculateDiscount } from "./pricing";
describe("calculateDiscount", () => {
describe("no discounts", () => {
it("returns zero discount for non-VIP with no coupon", () => {
const result = calculateDiscount(100, null, false);
expect(result.discount).toBe(0);
expect(result.finalPrice).toBe(100);
});
});
describe("VIP discount", () => {
it("applies 10% discount for VIP users", () => {
const result = calculateDiscount(100, null, true);
expect(result.discount).toBe(10);
expect(result.finalPrice).toBe(90);
});
});
describe("coupon codes", () => {
it("applies 20% discount for SAVE20 coupon", () => {
const result = calculateDiscount(100, "SAVE20", false);
expect(result.discount).toBe(20);
expect(result.finalPrice).toBe(80);
});
it("applies flat $10 discount for FLAT10 coupon", () => {
const result = calculateDiscount(100, "FLAT10", false);
expect(result.discount).toBe(10);
expect(result.finalPrice).toBe(90);
});
it("ignores invalid coupon codes", () => {
const result = calculateDiscount(100, "INVALID", false);
expect(result.discount).toBe(0);
expect(result.finalPrice).toBe(100);
});
});
describe("stacking discounts", () => {
it("stacks VIP and coupon discounts", () => {
const result = calculateDiscount(100, "SAVE20", true);
// 10% VIP + 20% coupon = 30%
expect(result.discount).toBe(30);
expect(result.finalPrice).toBe(70);
});
});
describe("discount cap", () => {
it("caps total discount at 50% of subtotal", () => {
// VIP (10%) + SAVE20 (20%) on small amount
// where FLAT10 would exceed 50%
const result = calculateDiscount(15, "FLAT10", true);
// VIP: 1.50 + FLAT10: 10 = 11.50, but cap is 7.50
expect(result.discount).toBe(7.5);
expect(result.finalPrice).toBe(7.5);
});
});
describe("edge cases", () => {
it("handles zero subtotal", () => {
const result = calculateDiscount(0, "SAVE20", true);
expect(result.discount).toBe(0);
expect(result.finalPrice).toBe(0);
});
it("handles very small amounts", () => {
const result = calculateDiscount(0.01, null, true);
expect(result.discount).toBe(0);
expect(result.finalPrice).toBe(0.01);
});
it("rounds currency values correctly", () => {
const result = calculateDiscount(33.33, "SAVE20", false);
expect(result.discount).toBe(6.67);
expect(result.finalPrice).toBe(26.66);
});
});
});
Testing Strategies by Type
| Test Type | AI Effectiveness | Prompt Strategy | Human Review Focus |
|---|---|---|---|
| Unit Tests | Excellent | Specify edge cases and expected behavior | Verify assertions match intended behavior |
| Integration Tests | Good | Describe the flow and expected outcomes | Check mock boundaries and real-world accuracy |
| E2E Tests | Moderate | Provide page URLs, user flows, selectors | Verify selectors are stable, flows match UX |
| Snapshot Tests | Excellent | Just ask — AI knows the pattern | Verify snapshots capture meaningful structure |
| Performance Tests | Moderate | Specify thresholds and measurement points | Validate benchmarks are realistic |
Identifying Untested Code Paths
# Ask Claude Code to find gaps
> Run our test coverage report and identify the 5 most critical
files with low coverage. For each file, list the specific
untested functions and branches. Then write tests for the
most important untested paths.
# Focus on risk, not coverage percentage
> Look at app/lib/payments.ts. Which code paths handle error
scenarios? Are all error paths tested? Write tests for any
untested error handling code — these are the paths most likely
to fail in production.
The Biggest Mistake: Trusting AI Assertions
The most dangerous AI testing mistake is not reviewing the assertions. AI generates a test that passes, and you assume it is correct. But the test might be asserting the wrong thing — it tests that the function returns what it currently returns, not what it should return. Always read each expect() statement and ask: "Is this the correct expected value, or just the current value?"
Pro Tip: Write Tests First, Then Implement
AI makes TDD (Test-Driven Development) much more practical. Ask Claude to write tests first based on your requirements, review and correct the test expectations, then ask Claude to implement the code that makes the tests pass. This ensures the implementation matches your intent, not the other way around.
Summary
AI eliminates the time excuse for skipping tests. Generate comprehensive test suites in minutes, but always review assertions for correctness. Focus your review energy on verifying that tests assert the right behavior, not just the current behavior. Use AI to find coverage gaps, generate edge cases, and write the boilerplate. Use your judgment to ensure the tests actually protect against real bugs.