Assertion coverage without mutation testing
Your tests pass. The total is wrong. Here's the missing assertion—and what coverage reports can't tell you.

Your tests pass. Line coverage is 100%. An order total is wrong, and nothing fails.
The test ran the calculation. It just never checked the answer. Assertion coverage looks for that gap: code your tests execute without checking what it does.
How 100% code coverage can miss a bug
Here’s a checkout function:
export function checkout(price, quantity) {
const total = price * quantity;
return { status: 'confirmed', total };
}
And a passing test:
import assert from 'node:assert/strict';
import test from 'node:test';
import { checkout } from './checkout.js';
test('confirms an order', () => {
const order = checkout(25, 2);
assert.equal(order.status, 'confirmed');
});
Every line runs. But the test would still pass if the total were 49, 0, or 500. It only checks order.status.

One assertion changes that:
assert.equal(order.total, 50);
Now 49 fails. Line coverage hasn’t moved. The test got better anyway.
What is assertion coverage?
For this article, assertion coverage means connecting executed source code to the checks that observe its results. In the example, the status is checked; the calculation is not.
That is different from counting assertions. Jest’s expect.assertions(2) catches a test that never reaches its two expected assertions. But two checks of the status would still leave the total unchecked.
It also isn’t proof that a test is strong enough. assert.ok(order.total) accepts both 49 and 50. You still need to check the right thing against the right expectation.
Mutation testing vs code coverage
These tools answer different questions:
| Technique | What it tells you |
|---|---|
| Line and branch coverage | Which code and alternatives ran? |
| MC/DC | Did each condition independently affect a decision? |
| Assertion coverage | Which executed code is connected to a check? |
| Mutation testing | Do the tests fail when specific code changes are introduced? |
Keep your existing coverage tools. Vitest’s V8 and Istanbul providers help you find unexecuted code. MC/DC goes deeper into missing condition cases. Neither, by itself, tells you that the order total was checked.
Mutation testing deliberately changes the program and reruns tests. Replace price * quantity with price + quantity: the total becomes 27. Our original test passes; the improved test fails.
That’s useful experimental evidence. The tradeoff is running changed versions of the program. Tools reduce that work: Stryker supports per-test coverage and incremental results.
Finding missing assertions without changing the code
You can also work backward from the assertions: what value does each check read, and which code produced it? Recorded execution helps tie that explanation to a test that actually ran.
This idea predates coding agents. Checked-coverage research uses dynamic slicing to find executed statements that influence a test’s checks. An agent can instead read the code and tests and explain those connections. That’s a review, not a mechanically computed slice or a proof.

The distinction matters: tracing can reveal an unchecked total without running a mutant. Mutation testing can demonstrate that a particular wrong total escapes the test. One does not replace the other.
A test that said it checked trimming
In a TodoMVC browser-test review, the agent pointed out a less obvious gap:
await add(page, ' Buy milk ');
await expect(page.locator('.todo-list li label')).toHaveText('Buy milk');
The test was called “trims a new title.” But Playwright’s string text matcher normalizes whitespace. It could pass even if the app kept the spaces.
We checked: removing the application’s trim() call left all 12 tests passing. Adding an exact check caught the change:
expect(await page.locator('.todo-list li label').textContent()).toBe('Buy milk');
The failure showed expected "Buy milk", received " Buy milk ". Restoring the trim made the stronger test pass.
The agent identified the weakness; we then introduced the fault and added this assertion to verify it. That’s one useful finding, not proof that another agent or mutation tool would miss it.
The useful question wasn’t “does this test have an assertion?” It was “would this assertion notice if trimming stopped working?”
When running the tests is the expensive part
A browser test may start the app, sign in, create an order and wait for a background job. Repeating that journey for many mutations can be expensive.
Can an agent find a missing check from an existing run before spending time on more executions? Count both its map review and the test run that checks its fix.
Repository size alone doesn’t answer this. A large project with fast unit tests may be inexpensive to mutation-test; a small app with shared browser journeys may not. Include Stryker’s test selection and incremental reuse, not just cold runs.
Targeted mutation testing was practical here
We narrowed Stryker 10.0.0 to TodoMVC’s item-creation function, keeping all 12 browser tests. It finished 12 mutations in 30.9 seconds. Removing trim() survived—the same gap the agent had identified. With the exact-text assertion added, that mutation was killed; the forced rerun took 29.6 seconds. Unchanged incremental runs took 2.8–2.9 seconds.
Because Stryker’s command adapter cannot detect changed tests, we forced the rerun after adding the assertion. This was a replay of a known finding, not a blind discovery contest. It shows why targeting and reuse belong in a fair comparison.
Recording is only part of the cost
In a clean follow-up on 14 September 2026, a local Supercov candidate recorded the suite and produced its first report in 4.1 seconds. That report had no assertion explanations yet.
The agent then spent 5 minutes 18 seconds reviewing the tests. It mapped 12 of 25 assertions: five mappings were current, seven had unresolved questions, and 13 assertions remained unmapped. The mapping-completeness check failed. The follow-up measurements separate recording, review, cost and completion.
That’s useful partial review, not a finished analysis. The candidate was unreleased when measured. These results do not establish a speed advantage for the complete workflow, and the full-suite review and targeted mutant set are different scopes.
Our earlier released-version checks also exposed limitations: Supercov 0.0.48 exceeded five minutes on Cookie’s 63,740 unit tests and failed to record the TodoMVC browser suites. Stryker completed Cookie, but its broad JavaScript browser run was still incomplete at five minutes, with 67 of 556 mutations tested. The initial report and reproduction files preserve those failures and all setup details. They are not a benchmark of large production apps.
Try it with Supercov
Supercov gives your coding agent recorded execution evidence to review:
- Run your tests. Supercov records executed code and passing assertions.
- Trace what they check. Your agent connects source statements to assertions and explains each connection.
- Review the gaps. Supercov checks that the explanation is current and that the statement and passing assertion ran in the same test.
The Assertions percentage counts measured source statements credited by that map, not the number of assertions. An unmapped statement needs investigation: a check might exist but not have been mapped yet. The agent’s explanation still needs review.
No mutants are generated or executed for this assessment. You still pay for the test run, recording overhead, and the agent’s reasoning.
Start with one useful gap. Add the missing assertion, check its expected value against the requirement, and rerun the suite. The goal is a test that catches a mistake it used to miss.
The assertion coverage guide has the commands and a prompt you can paste into your agent.