jestjs/jest · error · TypeError

Cannot spy on the `${methodKey}` property because it is not

Error message

Cannot spy on the `${methodKey}` property because it is not a function; ${typeOfOriginal} given instead.

What it means

Thrown as a TypeError by jest.spyOn when object[methodKey] exists and is truthy but typeof original !== 'function' (and it is not already a mock). spyOn only works on methods; for value properties you must use replaceProperty. The message is helpful: it names the actual type found and, for non-object values, suggests jest.replaceProperty(object, 'key', value) instead.

Source

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

    }

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

    const original = object[methodKey];

    if (!original) {
      throw new Error(
        `Property \`${String(
          methodKey,
        )}\` does not exist in the provided object`,
      );
    }

    if (!this.isMockFunction(original)) {
      if (typeof original !== 'function') {
        throw new TypeError(
          `Cannot spy on the \`${String(
            methodKey,
          )}\` property because it is not a function; ${this._typeOf(
            original,
          )} given instead.${
            typeof original === 'object'
              ? ''
              : ` If you are trying to mock a property, use \`jest.replaceProperty(object, '${String(
                  methodKey,
                )}', value)\` instead.`
          }`,
        );
      }

      const isMethodOwner = Object.prototype.hasOwnProperty.call(
        object,
        methodKey,
      );

View on GitHub (pinned to f49721c78e)

Solutions

  1. Use jest.replaceProperty(object, 'key', value) for value properties, then restore with .restore().
  2. If the property should be a method, fix the producer so it actually assigns a function.
  3. If you meant to intercept a getter, call jest.spyOn(object, 'key', 'get').

Example fix

// before
jest.spyOn(config, 'port'); // port is 3000
// after
jest.replaceProperty(config, 'port', 8080);
Defensive patterns

Strategy: type-guard

Validate before calling

if (typeof obj[methodKey] !== 'function') {
  // It's a value property -> use replaceProperty
  jest.replaceProperty(obj, methodKey, newValue);
} else {
  jest.spyOn(obj, methodKey);
}

Type guard

const isMethod = <T extends object, K extends keyof T>(o: T, k: K): o is T & Record<K, (...a: any[]) => any> =>
  typeof o[k] === 'function';

if (isMethod(obj, 'port')) {
  jest.spyOn(obj, 'port');
} else {
  jest.replaceProperty(obj, 'port', 8080);
}

Prevention

When it happens

Trigger: jest.spyOn(config, 'port') where config.port = 3000; jest.spyOn(user, 'name') where name is a string; jest.spyOn(arr, 'length') (length is a number); jest.spyOn(obj, 'list') where list is an array. The mocker refuses to wrap a non-function because there is no invocation to intercept.

Common situations: Confusing a property that holds data with a method that returns data; using spyOn where replaceProperty is the right tool; spying on a field whose value changed from a function to a constant after a refactor.

Related errors


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