Skip to content

Diagnostics

When an assertion fails, you want to see what actually happened — not just that it didn't match. deride's failure messages include the full recorded call history, with indices.

Failure message format

withArg, withArgs, withReturn, and every everyCall.* assertion produce output like this:

AssertionError [ERR_ASSERTION]: Expected greet to be called with: 'carol'
  actual calls:
  #0 ('alice')
  #1 ('bob')
  #2 (42, { deep: true })

Each call is numbered by invocation index so you can refer to it with expect.greet.invocation(N) if needed, and its arg list is rendered via node:util's inspect() (depth 3, so nested objects stay readable).

Zero-call case

If the method was never called, the message is explicit rather than a silent "no match found":

Expected greet to be called with: 'alice'
  (no calls recorded)

When a throw happened

If a recorded call threw, the history shows it too:

greet: 2 call(s)
  #0 greet('alice') -> 'hello alice'
  #1 greet(42) -> threw Error: invalid input

This form is the output of spy.method.printHistory() — drop it into a console.log while debugging and you'll see every call plus its return or throw.

Improving readability while debugging

If an assertion's standard message isn't enough, the .spy surface gives you multiple angles on the same data:

typescript
// Full formatted history
console.log(mock.spy.greet.printHistory())

// Raw CallRecord array — structured, non-throwing
console.table(mock.spy.greet.calls.map(c => ({
  args: JSON.stringify(c.args),
  returned: c.returned,
  threw: c.threw,
})))

// Snapshot-serialise to a stable shape
console.log(JSON.stringify(mock.spy.greet.serialize(), null, 2))

Snapshot testing the call log

For integration-style tests where exact call history is the assertion:

typescript
expect(mock.spy.greet.serialize()).toMatchSnapshot()

.serialize() produces deterministic output: keys sorted alphabetically, no timestamps, circular refs rendered as [Circular], functions as [Function: name], Dates as ISO strings, Maps/Sets as tagged objects. Safe for snapshot diffs across CI runs.

Node util inspect depth

Arguments are rendered with inspect(arg, { depth: 3 }) — deep enough to show useful structure in a typical call, shallow enough not to dump a whole ORM graph. If you have a call with genuinely huge args and want more, use .spy.greet.calls[i].args directly to explore in a debugger.

Colour and TTY detection

Failure messages don't apply ANSI colours — the output goes through Node's assert module and respects whatever the surrounding test runner does with its reporter. Vitest, jest, and node:test all colour-render their own error output from these messages.

Tests that help future-you

A few habits that pay off:

  1. Prefer withArg(match.*) over raw values when asserting on args that vary — the matcher's description field appears in failure messages, so Expected save to be called with: objectContaining({ id: number }) is far more useful than a literal dump.
  2. Use invocation(i) for per-call assertions when testing call sequences; the index in the message makes it obvious which call was the culprit.
  3. Drop printHistory() into a flake; the output is copy-pasteable and the full history often reveals whether the call count or the args were the real mismatch.
  4. Snapshot-serialise the call log for tests asserting on rich interaction patterns; a diff is clearer than N separate expect(...) statements.

Released under the MIT License.