Unit Test Generator
Generate high-quality unit tests through intelligent code analysis. This skill works with Python, JavaScript/TypeScript, Java, Go, C/C++, Rust, and C# — and can adapt to other languages by following the same principles.
Workflow
Follow these four phases in order. Do not skip phases — each one informs the next.
Phase 1: Project Discovery
First, understand the testing landscape of the project.
If the user points to a specific file, analyze that file and its immediate neighbors (same-directory modules it imports, parent package __init__.py or equivalent).
If the user asks to scan the project, run the bundled detection script first:
python <skill-path>/scripts/detect_project.py --root <project-root>
This outputs JSON with detected language, framework, source/test directories, existing test patterns, and mock libraries. Use this as your starting point.
If no project root is given, assume the current working directory.
Manual detection (when the script isn't applicable): look for these signals in order:
- Config files:
pytest.ini, jest.config., vitest.config., junit-platform.properties, go.mod, Cargo.toml, *.csproj - Existing test directories:
tests/, test/, __tests__/, spec/, *_test.go - Package files:
requirements.txt, package.json, pom.xml, build.gradle - Import style in source files to confirm language variant
Record your findings: language, test framework, source directory, test directory, naming conventions, and any existing test examples to mimic.
Phase 2: Code Analysis
Read each target source file thoroughly. For each function, method, or class, build a mental model covering:
- Signature & contracts — parameter types, return types, default values, variadic args, generics
- Control flow — if/else branches, switch/case, loops, early returns, recursion
- Data flow — what inputs flow to what branches, what outputs are produced
- Dependencies — external calls (APIs, DBs, file I/O), other modules, injected services
- Side effects — mutations, logging, event emission, state changes
- Error paths — raised/thrown exceptions, error returns, panic/recover, null/undefined handling
Prioritize analysis depth by complexity: functions with many branches or external calls get more scrutiny than simple getters.
Phase 3: Test Design
For each analyzed unit, design tests at three levels:
Level 1 — Happy Path (always include)
- One test per distinct valid input combination
- Verify correct return value and any expected side effects
- If the function has multiple independent code paths, cover each path
Level 2 — Edge & Boundary (include for all non-trivial units)
- Empty inputs (empty string, empty list, null, zero)
- Boundary values (max/min, off-by-one, zero-length collections)
- Type-adjacent values (float for int, string-like for string)
- Unicode/special characters for string inputs
- Large inputs that approach practical limits
Level 3 — Error & Exception (include when code raises/returns errors)
- Invalid argument types or values that trigger explicit error handling
- Dependency failure simulation (network down, file not found, DB timeout)
- State violations (calling method before initialization)
Additional considerations:
- Mock external dependencies at the test boundary — do not let tests hit real networks, databases, or filesystems
- Use parameterized/table-driven tests when the same logic applies to many input-output pairs
- For classes, test constructor validity, method interactions, and state transitions
- For async code, test both resolution and rejection paths
Phase 4: Generate & Write Tests
Write the test file following these rules:
File placement:
- Mirror the source file path under the test directory
- Python:
src/foo/bar.py → tests/foo/test_bar.py - JS/TS:
src/foo/bar.ts → src/foo/__tests__/bar.test.ts or tests/foo/bar.test.ts - Java:
src/main/java/com/foo/Bar.java → src/test/java/com/foo/BarTest.java - Go:
pkg/foo/bar.go → pkg/foo/bar_test.go (same package) - Follow the project's existing convention if one exists
Style & structure:
- Match the existing test style in the project (fixtures, helpers, naming)
- Use Arrange-Act-Assert (AAA) pattern for each test
- Give tests descriptive names that explain the scenario:
test___ - Group related tests in a describe/suite/class when the framework supports it
- Add one brief comment per test explaining what edge case or behavior it validates
For each test, include:
- Setup (Arrange) — prepare inputs, create mocks, set up test fixtures
- Execution (Act) — call the function/method under test
- Verification (Assert) — check return value and/or side effects with specific, meaningful assertions
Imports: Include all necessary imports at the top. Prefer the project's existing mocking library. If none is set up, use the standard one for the framework (see language references).
After writing, verify:
- The test file has the correct name and location
- All imports resolve to real modules
- Mocks target the correct dependency injection points
- Assertions test behavior, not implementation details
Language-Specific Guidance
When working with a particular language, read the corresponding reference file for framework-specific patterns, assertion styles, and mock setup:
| Language | Reference File |
|---|
| ---------- | --------------- |
| Python | references/python.md |
| JavaScript / TypeScript | references/javascript.md |
| Java | references/java.md |
| Go | references/go.md |
| C / C++ | references/cpp.md |
| Rust | references/rust.md |
| C# / .NET | references/csharp.md |
Read the relevant reference file before writing tests — it contains idiomatic patterns that a generic approach would miss. For languages not listed, infer conventions from any existing tests in the project, or use the xUnit-style (setup/teardown, AAA) pattern common across most frameworks.
Output
After generating tests, always report:
- Which files were analyzed and how many units (functions/methods) found
- Which test file was created/updated and where
- A summary of test cases generated, grouped by level (happy path / edge / error)
- Any units that were skipped and why (e.g., trivial getter, already well-tested, requires complex harness)
Constraints
- Never modify source files — only create or update test files
- Never generate tests that call real external services (network, database, third-party APIs)
- Do not generate tests for generated code, framework boilerplate, or third-party vendored code
- If a test file already exists, add new tests without removing or modifying existing ones unless the user asks
- Respect the project's existing test patterns and conventions over this skill's defaults