SupercovBlog

Engineering

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.

Tests pass. Total wrong. A checkout receipt shows $25 × 2 and an unchecked $49 total beneath a TEST PASSED label.

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.

The test checks that order.status is confirmed, but has no assertion for order.total.
The status is checked. The calculated total is not, even though its code ran.

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:

TechniqueWhat it tells you
Line and branch coverageWhich code and alternatives ran?
MC/DCDid each condition independently affect a decision?
Assertion coverageWhich executed code is connected to a check?
Mutation testingDo 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.

Assertion mapping: recorded run, agent traces, review gaps. Mutation testing: change code, run tests, check failures.
Assertion mapping traces existing checks through a recorded run. Mutation testing checks whether deliberately changed code makes tests fail.

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.

Try it with Supercov

Supercov gives your coding agent recorded execution evidence to review:

  1. Run your tests. Supercov records executed code and passing assertions.
  2. Trace what they check. Your agent connects source statements to assertions and explains each connection.
  3. 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. We haven’t published a head-to-head speed benchmark, so this isn’t a claim to be faster than every alternative.

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.