jestjs/jest · error · Error

Cannot use spyOn on a primitive value; ${typeOfObject} given

Error message

Cannot use spyOn on a primitive value; ${typeOfObject} given

What it means

Thrown by jest.spyOn when the first argument is null/undefined or a primitive (string, number, bigint, boolean, symbol). The guard is object == null || (typeof object !== 'object' && typeof object !== 'function'). spyOn must install a property descriptor on the target, which is impossible on primitives — they are passed by value and have no extensible own slots.

Source

Thrown at packages/jest-mock/src/index.ts:1280

  spyOn<
    T extends object,
    K extends ConstructorLikeKeys<T> | MethodLikeKeys<T>,
    V extends Required<T>[K],
  >(
    object: T,
    methodKey: K,
  ): V extends ClassLike | FunctionLike ? Spied<V> : never;

  spyOn<T extends object>(
    object: T,
    methodKey: keyof T,
    accessType?: 'get' | 'set',
  ): MockInstance {
    if (
      object == null ||
      (typeof object !== 'object' && typeof object !== 'function')
    ) {
      throw new Error(
        `Cannot use spyOn on a primitive value; ${this._typeOf(object)} given`,
      );
    }

    if (methodKey == null) {
      throw new Error('No property name supplied');
    }

    if (accessType) {
      return this._spyOnProperty(object, methodKey, accessType);
    }

    const original = object[methodKey];

    if (!original) {
      throw new Error(
        `Property \`${String(
          methodKey,

View on GitHub (pinned to f49721c78e)

Solutions

  1. Log the object reference just before spyOn to confirm it is defined and is the right holder: console.log(target, typeof target).
  2. Ensure the import resolves to the actual object instance (the module's exports), not a primitive re-exported from it.
  3. If you must observe a primitive, wrap it: spy on the container object's property instead of the value.
  4. For class/static methods, pass the class constructor (a function) rather than an instance or a primitive default.

Example fix

// before
jest.spyOn(Config.timeout, 'apply'); // Config.timeout is 5000 (number)
// after
jest.spyOn(Config, 'getTimeout'); // spy the method that returns it
Defensive patterns

Strategy: type-guard

Validate before calling

if (target == null || (typeof target !== 'object' && typeof target !== 'function')) {
  throw new Error(`spyOn target must be an object/function, got ${typeof target}`);
}
jest.spyOn(target, 'method');

Type guard

const isSpyable = (v: unknown): v is object | ((...a: any[]) => any) =>
  v != null && (typeof v === 'object' || typeof v === 'function');

if (isSpyable(target)) {
  jest.spyOn(target, 'method');
}

Prevention

When it happens

Trigger: jest.spyOn(null, 'foo'); jest.spyOn(42, 'toString'); jest.spyOn('hello', 'length'); jest.spyOn(true, 'valueOf'); jest.spyOn(undefined, 'method'). Often happens when the object comes from a lookup that returned undefined (e.g. jest.spyOn(Modules.User, ...) where Modules.User is undefined).

Common situations: Mock not initialized before the test runs; module export renamed so the imported binding is undefined; destructuring at module top-level captured undefined; attempting to spy on a value-typed constant rather than the object that holds it.

Related errors


AI-assisted analysis of jestjs/jest@f49721c78e (2026-08-03). Data as JSON: /data/errors/cc69cef008b89427.json. Report an issue: GitHub.