{"record":{"id":"3be1158c144a8be2","repo":"jestjs/jest","slug":"spyon-methodname-is-not-declared-writable-o","errorCode":null,"errorMessage":"<spyOn> : ${methodName} is not declared writable or has no setter\nUsage: spyOn(<object>, <methodName>)","messagePattern":"<spyOn> : (.+?) is not declared writable or has no setter\nUsage: spyOn\\(<object>, <methodName>\\)","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/jest-jasmine2/src/jasmine/spyRegistry.ts","lineNumber":121,"sourceCode":"      if (obj[methodName] && isSpy(obj[methodName])) {\n        if (this.respy) {\n          return obj[methodName];\n        } else {\n          throw new Error(\n            getErrorMsg(`${methodName} has already been spied upon`),\n          );\n        }\n      }\n\n      let descriptor;\n      try {\n        descriptor = Object.getOwnPropertyDescriptor(obj, methodName);\n      } catch {\n        // IE 8 doesn't support `definePropery` on non-DOM nodes\n      }\n\n      if (descriptor && !(descriptor.writable || descriptor.set)) {\n        throw new Error(\n          getErrorMsg(\n            `${methodName} is not declared writable or has no setter`,\n          ),\n        );\n      }\n\n      const originalMethod = obj[methodName];\n      const spiedMethod = createSpy(methodName, originalMethod);\n      let restoreStrategy;\n\n      if (Object.prototype.hasOwnProperty.call(obj, methodName)) {\n        restoreStrategy = function () {\n          obj[methodName] = originalMethod;\n        };\n      } else {\n        restoreStrategy = function () {\n          if (!delete obj[methodName]) {\n            obj[methodName] = originalMethod;","sourceCodeStart":103,"sourceCodeEnd":139,"githubUrl":"https://github.com/jestjs/jest/blob/8e6d128e4a278059ecddecaa97400b04c8ae5fd9/packages/jest-jasmine2/src/jasmine/spyRegistry.ts#L103-L139","documentation":"Thrown by SpyRegistry.spyOn when the property descriptor lacks both writable:true and a setter (spyRegistry.ts:120-126). Jest replaces the method via assignment (obj[methodName] = spy), so a read-only or accessor-less property cannot be overwritten by the standard path.","triggerScenarios":"spyOn on a frozen object, an object literal with a getter but no setter for that method, a class field declared readonly, or a property defined with writable:false via Object.defineProperty.","commonSituations":"Spying on methods of a frozen config object; ES2022 public class fields that are defined as non-writable; library that uses getters to lazy-initialise methods; TypeScript readonly fields compiled to non-writable descriptors.","solutions":["Spy on the property accessor instead: spyOn(obj, 'method', 'get') to use the property-spy path.","Define a setter on the object: Object.defineProperty(obj, 'method', { ...descriptor, set(v){}, configurable: true }) before spying.","Spy at the prototype level where the method may be writable: spyOn(MyClass.prototype, 'method').","Avoid freezing objects that need to be spied on, or clone them first."],"exampleFix":"// before\nspyOn(config, 'load'); // config is frozen, read-only\n// after\nspyOn(config, 'load', 'get'); // property spy on the getter","handlingStrategy":"validation","validationCode":"const d = Object.getOwnPropertyDescriptor(obj, methodName);\nif (d && !d.writable && !d.set) {\n  throw new Error(`${String(methodName)} is read-only; spy via the property accessor`);\n}\nspyOn(obj, methodName);","typeGuard":"const isWritable = (o: object, k: PropertyKey): boolean => {\n  const d = Object.getOwnPropertyDescriptor(o, k);\n  return !!d && (!!d.writable || typeof d.set === 'function');\n};","tryCatchPattern":null,"preventionTips":["Avoid freezing objects you intend to spy on.","Use the property-access spy form for getter-only properties.","Spy at prototype level where methods are often writable."],"tags":["jest","spyon","mocking","readonly","descriptor","frozen"],"backgroundTag":null,"analyzedSha":"8e6d128e4a278059ecddecaa97400b04c8ae5fd9","analyzedAt":"2026-08-10T18:11:27.960Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}