vitest-dev/vitest · error · AssertionError

${msg}

Error message

${msg}

What it means

AssertionError thrown by toHaveBeenCalled/toBeCalled when the spy was either not called at all (positive form) or was called when .not was used. The formatted message (msg) includes the spy name and, in the .not case, the call arguments via formatCalls.

Source

Thrown at packages/expect/src/jest-expect.ts:588

  def(['toHaveBeenCalled', 'toBeCalled'], function () {
    const spy = getSpy(this)
    const spyName = spy.getMockName()
    const callCount = spy.mock.calls.length
    const called = callCount > 0
    const isNot = utils.flag(this, 'negate') as boolean
    let msg = utils.getMessage(this, [
      called,
      `expected "${spyName}" to be called at least once`,
      `expected "${spyName}" to not be called at all, but actually been called ${callCount} times`,
      true,
      called,
    ])
    if (called && isNot) {
      msg = formatCalls(spy, msg)
    }

    if ((called && isNot) || (!called && !isNot)) {
      throw new AssertionError(msg)
    }
  })

  // manually compare array elements since `jestEquals` cannot
  // apply asymmetric matcher to `undefined` array element.
  function equalsArgumentArray(a: unknown[], b: unknown[]) {
    return a.length === b.length && a.every((aItem, i) =>
      jestEquals(aItem, b[i], [...customTesters, iterableEquality]),
    )
  }

  def(['toHaveBeenCalledWith', 'toBeCalledWith'], function (...args) {
    const spy = getSpy(this)
    const spyName = spy.getMockName()
    const pass = spy.mock.calls.some(callArg => equalsArgumentArray(callArg, args))
    const isNot = utils.flag(this, 'negate') as boolean

    const msg = utils.getMessage(this, [

View on GitHub (pinned to d568f8ce37)

Solutions

  1. Verify the code path that should invoke the spy is actually executed (e.g. the event is fired, the function is called).
  2. Confirm the spy is attached to the same reference the code uses (spyOn creates a new reference; re-imports may bypass it).
  3. If using .not, ensure no incidental call site triggers the spy elsewhere.

Example fix

// before
const onClick = vi.fn();
render(<Button />);
expect(onClick).toHaveBeenCalled();
// after
const onClick = vi.fn();
render(<Button onClick={onClick} />);
fireEvent.click(screen.getByRole('button'));
expect(onClick).toHaveBeenCalled();
Defensive patterns

Strategy: validation

Validate before calling

if (spy.mock.calls.length === 0) throw new Error('spy was never called — verify the code path that invokes it');

Prevention

When it happens

Trigger: expect(spy).toHaveBeenCalled() where spy.mock.calls.length === 0; or expect(spy).not.toHaveBeenCalled() where the spy was called one or more times. The condition (called && isNot) || (!called && !isNot) controls the throw.

Common situations: Asserting a callback was invoked but the event/path was never exercised (e.g. a click handler test where the click was not simulated, or a mocked module that was never imported by the code under test).

Related errors


AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03). Data as JSON: /data/errors/5f0724e9a24b52a4.json. Report an issue: GitHub.