Test Coverage Boost - From Zero to Hero Testing
🎯 When to Use This Skill
Use when you need to:
-
Add tests to untested code
-
Increase coverage for CI/CD requirements
-
Prepare for major refactoring
-
Meet team coverage goals (usually 70-80%)
-
Fix flaky or broken tests
-
Implement TDD practices
⚡ Quick Coverage Check
Check Current Coverage:
JavaScript (Jest/Vitest)
npm test -- --coverage
Python
pytest --cov=. --cov-report=html
Go
go test -cover ./...
Java
mvn clean test jacoco:report
Ruby
bundle exec rspec --coverage
.NET
dotnet test /p:CollectCoverage=true
📊 The Coverage Ladder (0% → 80%)
Level 0: No Tests (0% coverage)
Level 1: Critical Path (20% coverage)
Level 2: Happy Path (40% coverage)
Level 3: Edge Cases (60% coverage)
Level 4: Error Handling (80% coverage)
Level 5: Full Coverage (95%+)
🎯 Strategic Test Writing (80/20 Rule)
WITH MCP (Test Generator):
"Generate tests for the most critical untested functions" "Create tests to reach 80% coverage for [module]"
WITHOUT MCP - Priority Order:
- Test Business Critical Code First (20% effort → 80% value)
// Priority 1: Money/Payment handling test('calculateTotal should handle discounts correctly', () => { expect(calculateTotal(100, 0.2)).toBe(80); // 20% discount expect(calculateTotal(100, 0)).toBe(100); // No discount expect(calculateTotal(100, 1)).toBe(0); // 100% discount });
// Priority 2: Authentication/Authorization test('user should not access admin functions', () => { const user = { role: 'user' }; expect(() => adminFunction(user)).toThrow('Unauthorized'); });
// Priority 3: Data validation test('email validator should reject invalid emails', () => { expect(validateEmail('notanemail')).toBe(false); expect(validateEmail('test@example.com')).toBe(true); });
- The Test Writing Formula
// Universal Test Structure (AAA Pattern) test('should [expected behavior] when [condition]', () => { // Arrange - Set up test data const input = { /* test data / }; const expected = { / expected result */ };
// Act - Execute the function const result = functionUnderTest(input);
// Assert - Verify the result expect(result).toEqual(expected); });
📝 Test Patterns by Code Type
- Pure Functions (Easiest to Test)
// Function to test function addTax(price, taxRate) { return price * (1 + taxRate); }
// Tests describe('addTax', () => { test('should add tax correctly', () => { expect(addTax(100, 0.1)).toBe(110); });
test('should handle zero tax', () => { expect(addTax(100, 0)).toBe(100); });
test('should handle negative price', () => { expect(addTax(-100, 0.1)).toBe(-110); }); });
- Async Functions
// Function to test
async function fetchUser(id) {
const response = await api.get(/users/${id});
return response.data;
}
// Tests describe('fetchUser', () => { test('should fetch user successfully', async () => { // Mock the API api.get = jest.fn().mockResolvedValue({ data: { id: 1, name: 'John' }, });
const user = await fetchUser(1);
expect(user.name).toBe('John');
});
test('should handle API errors', async () => { api.get = jest.fn().mockRejectedValue(new Error('Network error'));
await expect(fetchUser(1)).rejects.toThrow('Network error');
}); });
- Classes/Objects
// Class to test class ShoppingCart { constructor() { this.items = []; }
addItem(item) { this.items.push(item); }
getTotal() { return this.items.reduce((sum, item) => sum + item.price, 0); } }
// Tests describe('ShoppingCart', () => { let cart;
beforeEach(() => { cart = new ShoppingCart(); });
test('should start empty', () => { expect(cart.items).toHaveLength(0); });
test('should add items', () => { cart.addItem({ name: 'Book', price: 10 }); expect(cart.items).toHaveLength(1); });
test('should calculate total', () => { cart.addItem({ name: 'Book', price: 10 }); cart.addItem({ name: 'Pen', price: 2 }); expect(cart.getTotal()).toBe(12); }); });
- API Endpoints
// Express endpoint app.get('/api/users/:id', async (req, res) => { const user = await User.findById(req.params.id); if (!user) return res.status(404).json({ error: 'Not found' }); res.json(user); });
// Tests describe('GET /api/users/:id', () => { test('should return user when exists', async () => { const response = await request(app).get('/api/users/1').expect(200);
expect(response.body).toHaveProperty('id', 1);
});
test('should return 404 when user not found', async () => { const response = await request(app).get('/api/users/999').expect(404);
expect(response.body.error).toBe('Not found');
}); });
🚀 Rapid Coverage Improvement Strategy
Day 1: Foundation (0% → 20%)
1. Setup test infrastructure
npm install --save-dev jest @types/jest
2. Create test script
package.json
"scripts": { "test": "jest", "test:coverage": "jest --coverage", "test:watch": "jest --watch" }
3. Write first test for main function
echo "describe('app', () => { test('should start', () => { expect(true).toBe(true); }); });" > app.test.js
Day 2: Critical Paths (20% → 40%)
// Test the money flow test('payment processing flow', async () => { // Test the entire payment pipeline });
// Test user authentication test('login flow', async () => { // Test login process end-to-end });
Day 3: Happy Paths (40% → 60%)
// Test successful scenarios test('user can create account', () => {}); test('user can update profile', () => {}); test('user can delete account', () => {});
Day 4: Edge Cases (60% → 70%)
// Test boundaries and limits test('handles maximum input size', () => {}); test('handles empty input', () => {}); test('handles special characters', () => {});
Day 5: Error Cases (70% → 80%)
// Test error handling test('handles network timeout', () => {}); test('handles invalid data', () => {}); test('handles concurrent access', () => {});
🎯 Coverage Improvement Techniques
- Find Untested Code
Generate coverage report
npm test -- --coverage
Open HTML report
open coverage/index.html # Mac xdg-open coverage/index.html # Linux start coverage/index.html # Windows
Red lines = untested code
- Test the Untestable
Mocking External Dependencies:
// Mock database jest.mock('./database', () => ({ query: jest.fn(), connect: jest.fn(), }));
// Mock file system jest.mock('fs', () => ({ readFile: jest.fn((path, callback) => { callback(null, 'mock file content'); }), }));
// Mock HTTP requests jest.mock('axios'); axios.get.mockResolvedValue({ data: 'test' });
Testing Private Methods:
// Option 1: Test through public interface class Calculator { #privateMethod() { return 42; } publicMethod() { return this.#privateMethod(); } }
test('private method via public', () => { const calc = new Calculator(); expect(calc.publicMethod()).toBe(42); });
// Option 2: Extract to separate testable function function complexLogic(input) { /* ... */ } export { complexLogic }; // Test directly
- Parameterized Tests (Test Multiple Cases)
// Jest test.each([ [1, 1, 2], [1, 2, 3], [2, 2, 4], ])('add(%i, %i) = %i', (a, b, expected) => { expect(add(a, b)).toBe(expected); });
// Python pytest @pytest.mark.parametrize("input,expected", [ (1, 2), (2, 4), (3, 6), ]) def test_double(input, expected): assert double(input) == expected
📊 Coverage Goals by Project Type
Web Application
-
Controllers/Routes: 90%+ (critical paths)
-
Business Logic: 85%+ (core features)
-
Utilities: 95%+ (pure functions)
-
UI Components: 70%+ (focus on logic)
-
Database Models: 80%+ (validations)
API Service
-
Endpoints: 95%+ (all routes)
-
Middleware: 90%+ (auth, validation)
-
Services: 85%+ (business logic)
-
Error Handlers: 100% (critical)
Library/Package
-
Public API: 100% (all exports)
-
Core Logic: 95%+ (main features)
-
Edge Cases: 90%+ (robustness)
-
Examples: 100% (documentation)
💡 Pro Tips
Speed Up Test Writing:
Generate test boilerplate
VS Code: Install "Jest Snippets" extension
Type: "desc" → describe block
Type: "test" → test block
Type: "exp" → expect statement
Test Data Builders:
// Create reusable test data class UserBuilder { constructor() { this.user = { id: 1, name: 'Test User', email: 'test@example.com', }; }
withName(name) { this.user.name = name; return this; }
withEmail(email) { this.user.email = email; return this; }
build() { return this.user; } }
// Usage const user = new UserBuilder().withName('John').withEmail('john@example.com').build();
Coverage Exclusions:
// Istanbul ignore comments /* istanbul ignore next / // Ignore next line / istanbul ignore if / // Ignore if block / istanbul ignore else */ // Ignore else block
// Jest coverage config // package.json "jest": { "coveragePathIgnorePatterns": [ "node_modules", "test-fixtures", ".mock.js" ], "coverageThreshold": { "global": { "branches": 80, "functions": 80, "lines": 80, "statements": 80 } } }
🎯 Success Checklist
Your tests are good when:
-
✅ Coverage > 80%
-
✅ Tests run fast (< 1 minute)
-
✅ Tests are deterministic (no flakiness)
-
✅ Tests are readable (clear descriptions)
-
✅ Tests catch real bugs
-
✅ CI/CD pipeline includes tests
-
✅ Team writes tests for new features
Remember: 80% coverage with good tests > 100% coverage with bad tests! 🎯