We spent 6 months writing tests. 100% coverage. Then we added a feature. 40 tests broke. It took 2 weeks to fix them. We changed our approach.
The Problem with 100% Coverage
Tests should give confidence. But too many tests:
- Break when you refactor (even if code still works)
- Take forever to run
- Slow down every PR
- Are hard to understand
The Better Approach
Focus on behavior, not implementation. Test what matters:
1. Test the “Happy Path”
test('login with valid credentials redirects to dashboard', () => {
const { getByRole } = render(<Login />);
fireEvent.change(getByRole('textbox', { name: /email/i }), 'test@example.com');
fireEvent.change(getByRole('textbox', { name: /password/i }), 'password123');
fireEvent.click(getByRole('button', { name: /login/i }));
expect(window.location.pathname).toBe('/dashboard');
});
2. Test Edge Cases That Actually Happen
What happens when the network fails? What about invalid input?
3. Test Critical Business Logic
Don’t test simple getters. Test complex logic:
// Test this - complex calculation
function calculateShipping(order) { ... }
// Don't test this - simple getter
function getUserName(user) { return user.name; }
4. Integration Tests > Unit Tests
One good integration test is worth 10 unit tests. Test how things connect.
The Rule of Thumb
When you fix a bug, write a test. When you add a feature, write a test. Don’t aim for a number. Aim for confidence.
Our Current State
- ~70% coverage (down from 100%)
- Tests run in < 30 seconds
- Almost never break on refactors
- Developers actually write tests