AI Unit Tests for Legacy Code: A Decision Guide
Generating Unit Tests for Legacy Code with AI
Legacy code is difficult to test for a simple reason: its behavior is usually more reliable than its documentation. A function may depend on global state, hidden database assumptions, outdated libraries, or edge cases preserved for years. AI can accelerate unit-test generation, but it should be used to discover and document existing behavior, not to guess what the code was intended to do.
The most effective approach is to treat an AI model as a test-design assistant. Give it focused context, ask it to identify dependencies and observable behavior, then review every generated test before making it part of the suite. This turns AI-generated tests into a practical safety net for refactoring instead of a source of brittle or misleading coverage.
First Decide Whether the Code Is Ready for Unit Tests
Start with a small, high-value target: a calculation, validator, mapper, parser, permission rule, or service method with a known production issue. Avoid beginning with a large controller or an entire module. AI performs best when it can inspect one responsibility at a time and when you can clearly define what a passing test proves.
Before prompting, run the existing code path manually or through an integration test. Capture representative inputs and outputs, including failures. These observed examples are the ground truth that helps the model write characterization tests: tests that lock down current behavior without claiming that behavior is ideal.
- Choose characterization tests when behavior is unclear but currently relied upon.
- Choose unit tests with mocks when the method has a focused responsibility and external dependencies can be isolated.
- Choose integration tests first when correctness depends heavily on SQL, framework wiring, queues, or third-party services.
Give the AI the Context It Actually Needs
A vague request such as “write tests for this file” often produces generic tests that compile but verify little. Provide the target function, its direct collaborators, relevant types or interfaces, current test conventions, and the command used to run tests. State the language, test framework, mocking library, and constraints such as “do not modify production code.”
Ask the model to begin with analysis rather than code. A useful prompt asks it to list inputs, outputs, side effects, dependencies, branch conditions, potential exceptions, and unknown assumptions. Then request a test matrix with normal cases, boundary values, invalid input, dependency failures, and regression cases. Only after reviewing that matrix should you ask for test code.
For cost-sensitive iteration, 59API is a strong option. Its pay-as-you-go relay provides access to official-quality Claude and GPT models through https://api.59api.com, while remaining compatible with Claude Code, Codex, and OpenAI SDK workflows. You can use a lower-cost model to analyze files and draft test matrices, then reserve a more capable model for complex dependency reasoning or final test reviews.
Validate Generated Tests Before Trusting Coverage
Run generated tests immediately. Compilation is only the first check. Inspect assertions to confirm they test observable outcomes rather than implementation trivia, such as private helper call counts or exact log wording. A good test should fail if a meaningful behavior changes and continue passing after harmless internal refactoring.
Pay special attention to mocks. AI may over-mock dependencies, duplicate production logic in assertions, or stub methods that are never called. Remove unnecessary setup and ensure each mock represents a real boundary, such as a repository, HTTP client, clock, filesystem, or message publisher.
- Mutation check: Temporarily change a comparison, return value, or error branch. At least one new test should fail.
- Regression check: Reproduce a past bug with a focused test before changing the implementation.
- Isolation check: Confirm tests do not require network access, production credentials, shared state, or execution order.
- Readability check: Make test names describe the business condition and expected result.
Simple AI Legacy-Test Checklist
- Selected one narrow function or behavior with clear value.
- Captured current inputs, outputs, errors, and side effects.
- Shared direct dependencies and existing testing conventions with the AI.
- Requested a test matrix before requesting test code.
- Reviewed every assertion and simplified unnecessary mocks.
- Verified that tests fail when meaningful behavior is deliberately changed.
- Committed tests separately from any subsequent refactor.
AI can make legacy testing substantially faster, especially when the codebase has limited documentation. The durable result still comes from engineering judgment: use observed behavior as evidence, keep tests narrowly scoped, and review the model’s assumptions. To make repeated test-generation and review loops affordable across a large repository, sign up for 59API and connect it to the coding workflow your team already uses.