vitest-dev/vitest · error · TypeError

is not a spy or a call to a spy

Error message

${utils.inspect(resultSpy)} is not a spy or a call to a spy

What it means

`toHaveBeenCalledBefore(otherSpy)` validates that its argument is itself a spy before comparing invocation order. If the argument cannot act as a mock, it throws this `TypeError`. The matcher cannot read call data from a non-mock, so it aborts before ordering checks.

Solutions

  1. Ensure the argument is created with `vi.fn()` or `vi.spyOn()` before being passed in.
  2. Pass the mock instance itself, not a result or call object.
  3. Double-check that the same reference is used in the SUT and the assertion.

Example fix

// before
const a = vi.fn()
const b = () => {}
expect(a).toHaveBeenCalledBefore(b)

// after
const a = vi.fn()
const b = vi.fn()
expect(a).toHaveBeenCalledBefore(b)
Defensive patterns

Strategy: type-guard

Validate before calling

import { isMockFunction } from '@vitest/spy'
if (!isMockFunction(other)) { throw new Error('argument must be a vi.fn spy') }
expect(spyA).toHaveBeenCalledBefore(other)

Type guard

const isSpy = (v: unknown): v is import('vitest').Mock =>
  !!v && typeof v === 'function' && 'mock' in (v as any)

Prevention

When it happens

Trigger: Calling `expect(spyA).toHaveBeenCalledBefore(plainFn)` where `plainFn` was never wrapped with `vi.fn()`/`vi.spyOn()`; passing a mock's return value or a function reference that lost its mock metadata.

Common situations: Comparing two collaborators but forgetting to wrap the second; refactoring that replaced a `vi.fn()` with a normal function; asserting against an imported function that was not mocked.

Related errors


AI-assisted analysis of vitest-dev/vitest@1fa9837ec2 (2026-08-11). Data as JSON: /api/errors/6e3b81876c4b7ac9. Report an issue: GitHub.

Appendix: source

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

    if (beforeInvocationCallOrder.length === 0) {
      return !failIfNoFirstInvocation
    }

    if (afterInvocationCallOrder.length === 0) {
      return false
    }

    return beforeInvocationCallOrder[0] < afterInvocationCallOrder[0]
  }

  def(
    ['toHaveBeenCalledBefore'],
    function (resultSpy: MockInstance, failIfNoFirstInvocation = true) {
      const expectSpy = getSpy(this)

      if (!isMockFunction(resultSpy)) {
        throw new TypeError(
          `${utils.inspect(resultSpy)} is not a spy or a call to a spy`,
        )
      }

      this.assert(
        isSpyCalledBeforeAnotherSpy(
          expectSpy,
          resultSpy,
          failIfNoFirstInvocation,
        ),
        `expected "${expectSpy.getMockName()}" to have been called before "${resultSpy.getMockName()}"`,
        `expected "${expectSpy.getMockName()}" to not have been called before "${resultSpy.getMockName()}"`,
        resultSpy,
        expectSpy,
      )
    },
  )
  def(

View on GitHub (pinned to 1fa9837ec2)