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
- Verify the code path that should invoke the spy is actually executed (e.g. the event is fired, the function is called).
- Confirm the spy is attached to the same reference the code uses (spyOn creates a new reference; re-imports may bypass it).
- 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
- Trigger the code path explicitly (event, call, import) before asserting.
- Confirm the spy is on the same reference the code under test resolves to.
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
- ${utils.inspect(assertion._obj)} is not a spy or a call to a
- ${formatCalls(spy, msg, args)}
- ${formatReturns(spy, results, msg, value)}
- ${utils.inspect(resultSpy)} is not a spy or a call to a spy
- Cannot use `${shorthand}` when called with `new`. Use `mockI
AI-assisted analysis of vitest-dev/vitest@d568f8ce37 (2026-08-03).
Data as JSON: /data/errors/5f0724e9a24b52a4.json.
Report an issue: GitHub.