denoland/deno · error · AssertionError

ERR_ASSERTION

ERR_ASSERTION

Error message

Functions were not called the expected number of times

What it means

tracker.verify() runs report(), which compares each tracked function's actual call count against its expected count. Any mismatch throws AssertionError: with exactly one failure the message is that failure's own message; with two or more it is the aggregate 'Functions were not called the expected number of times', and err.details carries every mismatch (including expected/actual counts and the registered stack). This is the intended failure mode of a behavioral assertion, not an infrastructure error.

Source

Thrown at ext/node/polyfills/internal/assert/calltracker.js:160

    const errors = [];
    for (const context of new SafeSetIterator(this.#callChecks)) {
      const message = context.report();
      if (message !== undefined) {
        ArrayPrototypePush(errors, message);
      }
    }
    return errors;
  }

  verify() {
    const errors = this.report();
    if (errors.length === 0) {
      return;
    }
    const message = errors.length === 1
      ? errors[0].message
      : "Functions were not called the expected number of times";
    throw new AssertionError({
      message,
      details: errors,
    });
  }
}

return {
  CallTracker,
};
})();

View on GitHub (pinned to 9ad36f7a2c)

Solutions

  1. Read err.details (or call tracker.report()) to see which function mismatched, with expected vs actual counts and the registration stack.
  2. Make the code under test call the wrapper tracker.calls() returned — reassign fn = tracker.calls(fn) so the real path is intercepted.
  3. Await all async operations before verify(), and pass the correct expected count for legitimately-variable call patterns.
  4. Use tracker.report() when you want failures as data without throwing.

Example fix

// before
const save = tracker.calls(repo.save, 1); // wrapper never installed
await api.createUser(input);              // repo.save called on original path
tracker.verify();                          // AssertionError

// after
repo.save = tracker.calls(repo.save, 1);  // intercept the real path
await api.createUser(input);
tracker.verify();                          // passes
Defensive patterns

Strategy: validation

Validate before calling

const failures = tracker.report();
if (failures.length > 0) {
  for (const f of failures) console.error(f.message, 'actual:', f.actual, 'expected:', f.expected);
} else {
  tracker.verify();
}

Try / catch

try {
  tracker.verify();
} catch (err) {
  if (err.code === 'ERR_ASSERTION' && Array.isArray(err.details)) {
    for (const d of err.details) console.error(d.message);
  } else throw err;
}

Prevention

When it happens

Trigger: A wrapper created with tracker.calls(fn, 2) invoked 0, 1, or 3 times by the time verify() runs; async work not awaited before verify(); the code under test calling the original fn instead of the wrapper returned by calls().

Common situations: Event handlers not firing (wrong event name, listener attached after emit); missing await on promises before verification; stale expected counts after refactors.

Related errors


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/53870b29e35e9e9b. Report an issue: GitHub.