{"record":{"id":"58263617c402be61","repo":"jestjs/jest","slug":"cannot-spy-on-the-string-methodkey-property","errorCode":null,"errorMessage":"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.`}","messagePattern":"Cannot spy on the `(.+?)` property because it is not a function; (.+?) given instead\\.(.+?)', value\\)` instead\\.`\\}","errorType":"validation","errorClass":"TypeError","httpStatus":null,"severity":"error","filePath":"packages/jest-mock/src/index.ts","lineNumber":1305,"sourceCode":"    }\n\n    if (accessType) {\n      return this._spyOnProperty(object, methodKey, accessType);\n    }\n\n    const original = object[methodKey];\n\n    if (!original) {\n      throw new Error(\n        `Property \\`${String(\n          methodKey,\n        )}\\` does not exist in the provided object`,\n      );\n    }\n\n    if (!this.isMockFunction(original)) {\n      if (typeof original !== 'function') {\n        throw new TypeError(\n          `Cannot spy on the \\`${String(\n            methodKey,\n          )}\\` property because it is not a function; ${this._typeOf(\n            original,\n          )} given instead.${\n            typeof original === 'object'\n              ? ''\n              : ` If you are trying to mock a property, use \\`jest.replaceProperty(object, '${String(\n                  methodKey,\n                )}', value)\\` instead.`\n          }`,\n        );\n      }\n\n      const isMethodOwner = Object.prototype.hasOwnProperty.call(\n        object,\n        methodKey,\n      );","sourceCodeStart":1287,"sourceCodeEnd":1323,"githubUrl":"https://github.com/jestjs/jest/blob/8e6d128e4a278059ecddecaa97400b04c8ae5fd9/packages/jest-mock/src/index.ts#L1287-L1323","documentation":"Thrown by jest.spyOn (as a TypeError) when the property exists but is not a function. spyOn only wraps functions; for value properties the message actively suggests jest.replaceProperty. The replaceProperty hint is suppressed when the original is an object.","triggerScenarios":"jest.spyOn(obj,'count') when obj.count = 5; jest.spyOn(config,'timeout') when timeout is a number; jest.spyOn(obj,'items') when items is an array.","commonSituations":"Confusing a data property with a method; the API changed and a method became a value; trying to spy on a getter-backed value without the 'get' access type.","solutions":["Use jest.replaceProperty(obj,'count',5) for value properties.","If the property is backed by a getter, call jest.spyOn(obj,'prop','get').mockReturnValue(value).","Confirm via typeof obj.method === 'function' before spying."],"exampleFix":"// before\njest.spyOn(config, 'timeout'); // timeout is a number\n// after\njest.replaceProperty(config, 'timeout', 1000);","handlingStrategy":"type-guard","validationCode":"if (typeof obj[methodKey] !== 'function') {\n  // value property -> use replaceProperty\n  jest.replaceProperty(obj, methodKey, value);\n} else {\n  jest.spyOn(obj, methodKey);\n}","typeGuard":"const isMethod = <T extends object>(o: T, k: keyof T): boolean =>\n  typeof o[k] === 'function';","tryCatchPattern":null,"preventionTips":["Check typeof obj.prop === 'function' before choosing spyOn vs replaceProperty.","For accessor-backed values, use spyOn(obj, prop, 'get'|'set').","Read the API surface of the module before writing spies."],"tags":["spy","mock","type-validation"],"backgroundTag":null,"analyzedSha":"8e6d128e4a278059ecddecaa97400b04c8ae5fd9","analyzedAt":"2026-08-10T18:11:27.960Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}