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 - •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