What does coverage measure?
Coverage records which parts of your code ran while the tests ran. Nothing more. The test runner adds a counter to each statement, runs the suite, and reports which counters stayed at zero. The Coverage view here shades every line that ran and marks the rest “never ran”, with a percentage at the top: lines that ran divided by lines that could have run.
Start the task and look at delivery.js. The single test orders 20 worth of bread for an address 5 km away. It passes through every if line, because each condition has to be checked, and ends at return 4. The pickup return, the free delivery return, the throw and the 2.50 return never run. Coverage is 56%, and the grey lines are an exact list of the situations nobody has tested.
That list is the useful part. A percentage is a summary for dashboards. The grey lines are a to-do list for you.
How do you use the grey lines?
Take each grey line and ask which input would reach it. return 0 after order.pickup needs an order marked for collection. The second return 0 needs a total of 40 or more. The throw needs an address further than 8 km. The 2.50 return needs a distance of 3 km or less. Write one test for each, with the right expected answer, and watch the shading spread.
Work from the top of the function down. The early returns block everything below them, so a test for a pickup order never reaches the distance rules. That is fine: each test is meant to follow one path. When a line stays grey after you add a test, the input is not doing what you think, which is worth knowing too.
test('collecting from the shop is free', () => {
expect(deliveryFee({ pickup: true, total: 12, distanceKm: 0 })).toBe(0);
});
test('refuses addresses further than 8 km', () => {
expect(() => deliveryFee({ total: 20, distanceKm: 9 })).toThrow('8 km');
});Why can 100% coverage still miss bugs?
Because a line that ran is not a line that was checked. Coverage counts execution. It has no idea whether your test looked at the answer, or whether it chose an input that would expose a wrong answer. You can reach 100% on deliveryFee() with an order of 50 for free delivery and 2 km for the short fee. Every line runs, every test passes, and the code could say > 40 instead of >= 40 without any test noticing.
Customers who spend exactly 40 would then pay for delivery, which is the very case the sign advertises. The bug sits on a line coverage marks as fully tested. Boundaries like this are the most common reason a well-covered suite still lets bugs through.
The Break It challenge goes further. Its tests reach 100% while one test has no expect() at all and two use matchers so loose that any string passes. Coverage rewards running code. Only assertions reward checking it.


What is branch coverage, and does the runner show it?
Statement coverage asks whether each statement ran. Branch coverage asks whether each decision went both ways: every if true and false, both sides of ? :, both sides of && and ||. It is stricter. A function like return order.pickup ? 0 : fee is one statement, so a single test gives it 100% statement coverage while the other branch never ran.
Real tools report both, plus function coverage and line coverage, in a table per file. The practice runner measures statements on lines that begin a statement, which is close to line coverage, and it does not report branches. Keep that in mind when a short one-line condition shows as covered: open the logic and check that a test exists for each outcome.
How do you turn coverage on in Vitest and Jest?
In Vitest, run npx vitest run --coverage. The first time, it asks you to install a coverage provider; the default one is @vitest/coverage-v8, which uses the coverage counters built into the V8 JavaScript engine. Jest has coverage built in: run npx jest --coverage. Both print a table in the terminal and can write an HTML report you open in a browser, with the same red and green shading as the view here.
You can also set thresholds, so the run fails when coverage drops below a number. Teams use them to stop coverage sliding over time. Set them slightly under where you are now and raise them as the suite grows. A threshold set to 100% tends to produce tests written only to shade lines, which is exactly the kind of test the Break It challenge warns about.
// vitest.config.js
export default {
test: {
coverage: {
provider: 'v8',
thresholds: { statements: 85, branches: 80 },
},
},
};What is mutation testing, and how is it different?
Mutation testing flips the question. Instead of asking which lines ran, it changes the code in small ways and asks whether any test fails. Change >= 40 to > 40, <= 3 to < 3, a 0 to a 1, or remove a line. Each changed copy is a mutant. If a test fails, the mutant is killed. If every test still passes, the mutant survived, and you have found a gap your coverage report could not show.
Check my work does a small version of this on every lesson. It runs your tests against a handful of broken copies of the code and tells you which bug they missed. Here two of those copies move the boundaries, which is why the task asks for an order of exactly 40 and a distance of exactly 3 km. Dedicated tools, such as Stryker for JavaScript, generate hundreds of mutants automatically. They are slow on big projects, so teams often run them on the most important modules only.
What coverage number should you aim for?
There is no magic figure. Around 80% is common for application code, and higher for code that handles money, dates, permissions or anything people complain about when it goes wrong. Lower numbers are fine for glue code that only wires other tested pieces together. Where the uncovered lines are tells you more. A missing test on an error message is a smaller risk than a missing test on the discount rule.
Use coverage as a flashlight. When you finish a feature, open the report and look at the grey lines in the files you touched. Ask whether each one is worth a test. Often the answer is yes, and occasionally a grey line reveals code that can never run, which you can delete.
- Look at grey lines in files you changed
- Add a test with a checked answer for each important one
- Test the exact boundary values as well as values well inside each range
- Treat a high percentage as the start of a review, never the end of one
What do AI-written tests do to coverage?
Ask an assistant to “get coverage to 100%” and it will. The tests it writes often call every function with typical inputs and assert something weak, such as toBeDefined() or toBeTruthy(), or nothing at all. The percentage jumps and the suite catches very little. When you review AI-written tests, ignore the coverage figure for a moment and read each expect(). Is the expected value an exact answer you could work out from the rules?
Then look for the edges. Assistants pick round, comfortable numbers: 50, 100, 2 km. They rarely choose 40 or 3 km unless you ask. Asking for “tests for each boundary in this function” gets much better results than asking for a coverage number.
Field notes
- Coverage shows which lines ran during the tests, and nothing about whether they were checked.
- Each line marked never ran is a situation no test has tried yet.
- A suite can reach 100% and still miss a
>written where>=was meant. - Test the exact boundary values, such as an order of 40 and 3 km.
Easy to get wrong
The first version runs the throw line, so coverage goes up, and then swallows the error without checking it. If the function stopped throwing, the test would still pass.
Both inputs reach the free delivery line, but only the exact boundary tells >= 40 apart from > 40. Choose the value where the rule switches.
A hard 100% target pushes people to write tests that run code without checking it. A slightly lower threshold plus a habit of reading the grey lines gives a better suite.
Questions people ask
Why does the coverage here sometimes differ from Vitest?
The practice runner counts statements on lines that start a statement and has no branch or function coverage. Vitest with the V8 provider measures at a finer level, so a long line with a condition inside can count differently. The grey lines still point at the same untested paths.
Do test files count towards coverage?
No. Coverage is measured on the code being tested. Vitest and Jest exclude test files by default, and you can exclude other files, such as configuration, in the settings.
Should I write tests for code that is hard to reach?
If a line can only run in a rare situation, it is often the line that breaks in production, so give it a test. If a line can never run at all, delete it. Coverage helps you tell those two cases apart.
Is mutation testing worth setting up on a small project?
Probably not as a tool. The idea is worth using by hand: for each important rule, imagine the most likely wrong version and make sure a test would fail on it. That is exactly what Check my work does here.