MC/DC coverage for JavaScript
A checkout function can have 100% line and branch coverage without a test for an expired session. Both return paths run. Both tests pass. But removing the expiry check still leaves the suite green. MC/DC coverage asks a more specific question: has each condition been shown to change the decision on its own? Here’s what that looks like in JavaScript, using a small example we ran with Supercov. The condition the tests miss This function allows checkout when a customer is signed in and their session has not expired: export function canCheckout(signedIn, expired) { if (signedIn && !expired) return true; return false; } The original tests check a valid session and a signed-out visitor: assert.equal(canCheckout(true, false), true); assert.equal(canCheckout(false, false), false); One test takes the true return path. The other takes the false path. Neither passes true for expired. If we accidentally change the condition to just signedIn, both tests still pass. An expired session would now be accepted. What MC/DC measures MC/DC stands for Modified Condition/Decision Coverage. A decision is the whole expression, signedIn && !expired. Its two conditions are signedIn and !expired. For each condition, MC/DC looks for a pair of executions showing that the condition independently changes the decision’s outcome. For this example, the useful cases are: Case signedIn expired Checkout allowed? Valid session true false true Signed out false false false Expired session true true false Compare the first two rows: changing signedIn changes the result. Compare the first and third: changing expired changes the result while signedIn stays true. The original suite has the first pair, but not the second. JavaScript short-circuits &&: when signedIn is false, it does not evaluate !expired. Coverage tools need to retain that distinction rather than treating a skipped condition as a false one. The precise witness rules also depend on the form of MC/DC being measured; Clang’s coverage documentation explains its masking approach. For this small example, the missing expired-session case is straightforward. Measure it with Supercov Run your existing test command through Supercov, then query the result: npx supercov — npm test npx supercov runs latest Everything after — is your project’s test command. Here, npm test runs the example’s two tests. This is the coverage summary from that recorded run: Coverage Lines 100.00% (3/3) Branches 100.00% (2/2) MC/DC 50.00% (1/2) These are Supercov’s counts for this file, not a claim that every coverage provider would print the same percentages. Different tools count branches differently. In particular, ordinary branch or condition counters are not the same as evidence that a condition independently changes a decision. To see what’s missing: npx supercov runs latest gaps npx supercov runs latest file src/session.js The file view identifies the condition: LINE STATUS SOURCE 2 PARTIAL signedIn && !expired Unobserved: no witness pair shows !expired independently changing the decision result That is enough information to choose a test. We need a signed-in customer whose session has expired. Add a test that checks the missing behavior In our recorded run, the coding agent added this test and left the application code unchanged: test(‘a signed-in visitor with an expired session cannot check out’, () => { assert.equal(canCheckout(true, true), false); }); The assertion matters. Calling the function would exercise the condition; checking the result verifies that this input is denied checkout. After rerunning the same suite, all three tests passed. The agent compared the new run with the baseline: npx supercov — npm test npx supercov diff