Claude Code Workflow: Why I Cut My Test Suite
The production logic was straightforward:
1. Validate and normalize input.
2. Verify company existence.
3. Persist the Job Opportunity.
Yet, my test suite was bloated with fixtures, fakes, spies, and mocks. I was writing assertions to prove that specific repository methods were never called, which felt like testing the implementation details rather than the outcome.
The Lean Test Strategy
I stripped the suite down to three critical application-service cases that actually protect business logic:
- Valid input is normalized and successfully created.
- Invalid input is caught before it hits the persistence layer.
- Missing Company IDs prevent the creation of orphan opportunities.
If these three fail, the system is fundamentally broken. Everything else was noise.
Avoiding the "Mock Trap"
I realized I was tempted to mock every single repository failure branch just to increase coverage. For example, I was considering writing tests to verify that:
if (!result.success) return result;returns the same result it received. This is a waste of time. TypeScript already constrains the types, and my PostgreSQL integration tests already verify how the system handles actual database failures. Adding more mocks doesn't increase confidence; it just increases the amount of code I have to update whenever I change a function signature.
My Practical AI Workflow for Testing
To avoid over-engineering tests in the future, I've adopted a specific hierarchy for my LLM agent and manual testing:
- Unit Tests: Reserved for complex business rules and decision-making logic.
- Integration Tests: Focused on actual persistence behavior (no mocks here, use a real test DB).
- E2E/Manual: Verifying the complete user journey.
- Regression Tests: Only added after a real-world bug is discovered to prevent it from returning.
I stopped optimizing for branch coverage percentages. Instead, I focus on whether a test justifies its maintenance cost. If the setup for a test is 50 lines of mock configuration for a 2-line logic check, the scope is wrong.
For those implementing similar workflows, here is a sample of how I now structure a lean service test to ensure it stays readable:
describe('JobOpportunityService', () => {
it('should prevent orphan opportunities when company is missing', async () => {
// Arrange: Minimal setup, no complex spies
const service = new JobOpportunityService(mockRepo);
const invalidData = { companyId: 'non-existent-uuid', title: 'Dev' };
// Act
const result = await service.create(invalidData);
// Assert: Check the outcome, not the internal call count
expect(result.success).toBe(false);
expect(result.error).toBe('COMPANY_NOT_FOUND');
});
});The goal is to reduce the fear of changing code. If the tests are too complex, you become afraid to change the tests, which eventually makes you afraid to change the feature.