Code Coverage
Code coverage measures how much of your code the test suite actually executes — which lines, branches, and functions ran during the tests. It's a useful diagnostic for finding untested code, and a common CI gate. But it's also one of the most misunderstood metrics: high coverage does not mean good tests, and chasing 100% can produce worthless tests that execute code without verifying anything. This page covers the coverage types, the tooling, and — most importantly — how to use coverage well.
The four coverage metrics
Metric | Measures |
|---|---|
Line | Which lines of code were executed |
Branch | Which branches of conditionals ( |
Function | Which functions were called |
Statement | Which statements were executed (finer than line) |
Generating coverage
# Jest — built in: jest --coverage # node:test — built in: node --test --experimental-test-coverage # Mocha — via c8 (or nyc): npm install --save-dev c8 c8 mocha
----------------|---------|----------|---------|---------|------------------- File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s ----------------|---------|----------|---------|---------|------------------- All files | 87.5 | 76.92 | 90.0 | 87.5 | pricing.js | 100 | 100 | 100 | 100 | userService.js | 80.0 | 66.67 | 85.7 | 80.0 | 42-48, 71 ----------------|---------|----------|---------|---------|-------------------
Setting a coverage threshold in CI
jest.config.js
export default {
collectCoverage: true,
coverageThreshold: {
global: {
branches: 80,
functions: 80,
lines: 80,
statements: 80,
},
// Stricter for critical modules:
'./src/auth/': { branches: 95, functions: 95, lines: 95 },
},
// Don't count files that don't need testing:
coveragePathIgnorePatterns: ['/node_modules/', '/migrations/', '/config/'],
}The 100% coverage trap
// This test gives 100% line coverage of add()...
test('add runs', () => {
add(2, 3) // ❌ no assertion! Coverage is 100%, but it tests NOTHING
})
// ...but this is the test that actually matters:
test('add returns the sum', () => {
assert.equal(add(2, 3), 5) // ✅ verifies behaviour, not just execution
})Using coverage well
Find gaps, don't chase a number — use the uncovered-lines report to spot missing tests.
Prioritise branch coverage — it reveals untested decision paths.
Higher bars for critical code — auth, payments, data integrity.
Exclude code that doesn't need tests — generated files, config, migrations.
Every covered line should have a meaningful assertion behind it.
Coverage is a floor, not a ceiling — 80% with good tests beats 100% with empty ones.