Unit Test Strategies and Generation
You ask the AI to “write unit tests for UserService” and it generates 40 tests. They all pass. Coverage is 95%. You feel good until a customer reports that users with plus signs in their email addresses cannot register. None of those 40 tests covered that case because the AI tested the happy path in 40 slightly different ways. Volume is not coverage. The right tests at the right boundaries catch bugs — and that requires specific, intentional prompting.
What You’ll Walk Away With
Section titled “What You’ll Walk Away With”- Prompt patterns that generate tests focused on behavior and edge cases, not just line coverage
- Mocking strategies that keep tests fast and reliable without over-mocking
- Techniques for testing error handling, concurrency, and boundary conditions
- Workflows for maintaining tests as code evolves
- Mutation testing integration to verify your tests actually catch bugs
The Behavior-First Approach
Section titled “The Behavior-First Approach”The most effective AI-generated tests describe what the code should do, not how it does it internally.
Open the file to test and use Agent mode:
@src/services/order.service.tsGenerate unit tests for OrderService.calculateTotal. Focus on BEHAVIOR:
1. "should apply percentage discount correctly" (10% off $100 = $90)2. "should apply fixed discount correctly" ($15 off $100 = $85)3. "should not allow total below zero" (discount > subtotal)4. "should calculate tax after discount" (tax on discounted amount, not original)5. "should handle empty cart" (zero items, no discount)6. "should round to 2 decimal places" (avoid floating point weirdness)7. "should reject negative quantities" (throw ValidationError)8. "should handle mixed currency items" (throw CurrencyMismatchError)
Test through the public API only. Do not mock internal methods.Mock only the database and external service dependencies.Follow patterns in @src/services/__tests__/payment.service.test.tsclaude "Read /src/services/order.service.ts and generate behavior-focused unit tests.
For calculateTotal, test these behaviors:1. Percentage discounts applied correctly2. Fixed-amount discounts applied correctly3. Total never goes below zero4. Tax calculated on discounted amount5. Empty cart handling6. Floating point precision (round to 2 decimals)7. Negative quantity rejection8. Mixed currency rejection
Mock ONLY: OrderRepository, TaxService, DiscountServiceDo NOT mock: internal methods, utility functionsUse Jest + TypeScript. Save to /src/services/__tests__/order.service.test.tsRun the tests after generating them. Fix any failures."Generate behavior-driven unit tests for OrderService.calculateTotal.
Test each business rule independently:- Discount application (percentage and fixed)- Tax calculation timing (after discount)- Boundary conditions (empty cart, zero total, max values)- Error conditions (negative quantities, currency mismatch)- Precision handling (floating point rounding)
Follow existing test conventions in the project.Run the test suite after generating. Fix any issues.Create a PR with the new tests.Intelligent Mocking Strategies
Section titled “Intelligent Mocking Strategies”The Boundary Rule: Mock at the Boundary, Not Internally
Section titled “The Boundary Rule: Mock at the Boundary, Not Internally”Test Data Factories
Section titled “Test Data Factories”Stop hardcoding test data. Use factories that generate realistic data with overridable defaults.
Testing Error Handling
Section titled “Testing Error Handling”Error handling is where most AI-generated tests are weakest. Prompt specifically for error scenarios.
Concurrency Testing
Section titled “Concurrency Testing”AI tools can generate concurrency tests that most developers skip.
Mutation Testing: Proving Your Tests Work
Section titled “Mutation Testing: Proving Your Tests Work”Coverage metrics lie. A test that executes a line does not necessarily verify its behavior. Mutation testing introduces small changes (mutations) to your code and checks if tests catch them.
-
Install Stryker Mutator
Terminal window npm install --save-dev @stryker-mutator/core @stryker-mutator/jest-runner @stryker-mutator/typescript-checker -
Configure for your project
Terminal window npx stryker init# Select Jest runner, TypeScript checker# Set mutate to: src/services/**/*.ts (start small) -
Run and analyze results
Terminal window npx stryker run# Review the HTML report - focus on surviving mutations -
Use AI to kill surviving mutants
Feed the surviving mutations back to the AI for targeted test generation.
Test Maintenance
Section titled “Test Maintenance”AI-Assisted Test Updates
Section titled “AI-Assisted Test Updates”When your implementation changes, AI can update the tests intelligently.
When This Breaks
Section titled “When This Breaks”“AI generates tests that all look the same.” You gave a generic prompt. Be specific about the scenarios you want tested. List the edge cases, error conditions, and boundary values explicitly.
“Tests pass locally but fail in CI.” Check for test isolation issues. AI-generated tests sometimes share mutable state between tests. Ensure beforeEach resets all mocks and test state. Add --runInBand to CI if tests have hidden parallelism issues.
“Mutation score is low despite high coverage.” Your tests are executing code without verifying its output. Focus on adding assertions for return values, side effects, and error conditions. Coverage without assertions is meaningless.
“Test suite is slow after AI generated hundreds of tests.” Review for redundancy. Ask the AI: “Analyze these tests and identify sets that cover the same code paths. Which tests can be removed without reducing mutation coverage?”