Test Refactor
Ask the user for the test file to refactor. Work on one test file at a time.
Process
Work through these steps in order, making a separate commit for each step that makes changes:
0. Count cyclomatic complexity (DO FIRST)
- Read the source file being tested
- Count decision points:
if,else,for,while,case,catch,&&,||,?: - Cyclomatic complexity = 1 + decision points
- This is your TARGET test count (approximately)
- Report: "Cyclomatic complexity: ~N paths, currently M tests"
- If M >> N, tests need consolidation
1. Identify public API and remove tests for internals
- First: identify what the PUBLIC API is (what external callers use)
- Functions exported only to enable testing are NOT public API - make them private
- Private methods tested indirectly through public interface
- If function A only calls function B internally, test through A, make B private
- Example:
buildBotRequestcallsbuildToolsForAgent→ test throughbuildBotRequest, unexportbuildToolsForAgent
2. Remove tests for getters/trivial code
- Only test core behavior, not trivial getters/setters
- Remove noise that doesn't provide safety net
3. Remove implementation detail tests
- Audit/logging callbacks → implementation detail, remove
- Caching behavior → implementation detail, remove
- Tests that verify Promise.all/parallel execution → testing the language, remove
- If behavior matters to users, keep; if it's internal wiring, remove
4. Consolidate to match cyclomatic complexity
- Target: ~1 test per code path through the function
- Merge tests covering same code path with different assertions
- Example: "returns response" + "propagates nestedTools" + "propagates suggestedActions" → one test checking all output fields
- Example: "MCP returns error" + "MCP throws exception" → one test for error handling
5. Fix test anti-patterns
- Tests testing implementation → refactor to test behavior
- Conditional logic in tests → split into separate tests
- Order-dependent tests → make independent
toHaveBeenCalledWithon non-void functions → stub return value instead (Query Command Separation: queries are stubs, commands are mocks)expect(arr).toHaveLength(n)followed by element check → remove length check, let element assertion fail directly- Imperative scaffolding inside tests — scan test bodies (arrange AND assert sections) for:
forloops building lookup maps/lists for assertionsnew HashMap<>()/new ArrayList<>()followed by puts/adds purely to enable an assertion- Manual element lookup by index (
list.get(0),list.get(1)) - Reflection (
setAccessible,Field.set) to populate test fixtures — replace with builders/setters on the production type Replace with fluent assertion idioms (AssertJfilteredOn(...).singleElement().satisfies(...),extracting(...),anySatisfy; JestarrayContaining/objectContaining) or stream collectors (Collectors.toMap).
- If test level seems wrong for component, ASK USER with recommendation
6. Improve test data
- Move large test objects to test data builders
- Keep only relevant data in each test
- Keep test data close to test for readability
7. Improve test names/assertions (ASK USER FIRST)
- Present 3 options for unclear tests:
- Rename test to describe behavior
- Improve assertion messages
- Both
- Wait for user decision
8. Refactor test structure
- Use AAA pattern (Arrange-Act-Assert)
- Separate sections with blank lines (NO comments)
- Extract helper functions for duplicated setup (NOT beforeEach)
- Use beforeEach only for variables scoped to describe block
9. Review mocking strategy
- Acceptance tests: Use mock web servers (http), TestContainers (DB)
- Unit tests: Only mock external dependencies (filesystem, random, time, http, DB)
- Prefer test doubles over mocking frameworks
- Use real objects when possible
After EACH step:
- Run tests automatically
- If tests fail, revert the change immediately
- Commit using:
. t <brief description>
Testing Philosophy
- TDD: Write tests first
- Acceptance tests for behavior, unit tests for complex pure functions
- Acceptance level depends on component:
- Microservices: Event or HTTP level
- UI: Route-level component rendering
- Test count ≈ cyclomatic complexity (one test per code path)
Output
- If no refactoring needed, report "No test refactoring needed"
- Otherwise provide brief summary (e.g., "Reduced 15 → 8 tests to match cyclomatic complexity, removed implementation detail tests")
Commit Format
Use Arlo's Commit Notation (ACN):
. t remove tests for private methods. t consolidate tests to match cyclomatic complexity. t remove implementation detail tests