100% code coverage with 0% bug detection is possible and common. Coverage is a flashlight in a dark roomβit shows where you've looked, not what you've found.
// Function to test function calculateDiscount(price, customerType) { if (customerType === 'premium') { return price * 0.8; // 20% off } else if (customerType === 'regular') { return price * 0.9; // 10% off } return price; // BUG: Should be price * 1.0 for clarity } // π± BAD: 100% coverage but catches nothing test('discount calculation', () => { // Covers all branches but doesn't verify correctness! calculateDiscount(100, 'premium'); calculateDiscount(100, 'regular'); calculateDiscount(100, 'new'); // No assertions! Still gets 100% coverage! }); // Still 100% coverage, still useless test('discount calculation with weak assertions', () => { const premium = calculateDiscount(100, 'premium'); const regular = calculateDiscount(100, 'regular'); const newCustomer = calculateDiscount(100, 'new'); expect(premium).toBeDefined(); expect(regular).toBeDefined(); expect(newCustomer).toBeDefined(); // Verifies nothing about correctness! }); // β GOOD: Lower coverage, actually catches bugs test('premium customers get 20% discount', () => { expect(calculateDiscount(100, 'premium')).toBe(80); expect(calculateDiscount(50, 'premium')).toBe(40); }); test('regular customers get 10% discount', () => { expect(calculateDiscount(100, 'regular')).toBe(90); expect(calculateDiscount(200, 'regular')).toBe(180); }); test('unknown customers pay full price', () => { expect(calculateDiscount(100, 'new')).toBe(100); expect(calculateDiscount(100, null)).toBe(100); expect(calculateDiscount(100, 'invalid')).toBe(100); });
// Original function function isAdult(age) { return age >= 18; } // Mutation testing introduces bugs (mutants): // Mutant 1: return age > 18; (boundary change) // Mutant 2: return age >= 17; (constant change) // Mutant 3: return age <= 18; (operator change) // Mutant 4: return true; (always true) // π± BAD: Weak tests that don't kill mutants test('age check', () => { expect(isAdult(25)).toBe(true); expect(isAdult(10)).toBe(false); // Doesn't test boundary! Mutants 1 & 2 survive! }); // β GOOD: Tests that kill all mutants test('adult age boundary', () => { expect(isAdult(17)).toBe(false); // Kills mutant 2 expect(isAdult(18)).toBe(true); // Kills mutant 1 expect(isAdult(19)).toBe(true); // Confirms boundary }); test('handles edge cases', () => { expect(isAdult(0)).toBe(false); // Kills mutant 3 expect(isAdult(-1)).toBe(false); // Kills mutant 4 expect(isAdult(100)).toBe(true); // Verifies upper range }); // Mutation testing tools: // JavaScript: Stryker // Java: PIT // Python: mutmut // C#: Stryker.NET
// π± BAD: Testing specific examples test('sorting works', () => { expect(sort([3, 1, 2])).toEqual([1, 2, 3]); expect(sort([5, 2, 8, 1])).toEqual([1, 2, 5, 8]); // What about edge cases we didn't think of? }); // β GOOD: Property-based testing import fc from 'fast-check'; test('sorting properties', () => { fc.assert( fc.property(fc.array(fc.integer()), (arr) => { const sorted = sort(arr); // Property 1: Same length expect(sorted.length).toBe(arr.length); // Property 2: Is ordered for (let i = 1; i < sorted.length; i++) { expect(sorted[i]).toBeGreaterThanOrEqual(sorted[i-1]); } // Property 3: Same elements (permutation) const origCounts = countElements(arr); const sortedCounts = countElements(sorted); expect(sortedCounts).toEqual(origCounts); }) ); }); // Tests with 100s of random inputs automatically!
// Unit Test (Fast, Focused) test('password validation', () => { expect(isValidPassword('abc')).toBe(false); // Too short expect(isValidPassword('abcdefgh')).toBe(false); // No numbers expect(isValidPassword('abcd1234')).toBe(false); // No special chars expect(isValidPassword('Abc123!@')).toBe(true); }); // Integration Test (Component Boundaries) test('user registration API', async () => { const response = await request(app) .post('/api/register') .send({ email: 'test@example.com', password: 'Abc123!@' }); expect(response.status).toBe(201); expect(response.body.userId).toBeDefined(); // Verify user was actually created const user = await db.users.findByEmail('test@example.com'); expect(user).toBeDefined(); }); // E2E Test (Critical Paths Only) test('complete purchase flow', async ({ page }) => { await page.goto('/shop'); await page.click('[data-product="widget"]'); await page.click('button:has-text("Add to Cart")'); await page.click('button:has-text("Checkout")'); await page.fill('#email', 'customer@example.com'); await page.fill('#card', '4242424242424242'); await page.click('button:has-text("Purchase")'); await expect(page).toHaveURL(//confirmation/); await expect(page.locator('h1')).toContainText('Thank You'); });
A 70% mutation score is better than 100% code coverage. It means your tests actually detect 70% of possible bugs.
For each production bug, ask: "Could a test have caught this?" If yes, write that test. Track the ratio over time.
Good failures: Actual bugs, breaking changes Bad failures: Flaky tests, brittle assertions Track: Real Bugs Found / Total Test Failures
Good tests fail with clear messages. If developers spend more than 2 minutes understanding a failure, the test needs better diagnostics.