{"id":"130caace7d3109a1","repo":"jestjs/jest","slug":"methodname-is-not-declared-writable-or-has-no-s","errorCode":null,"errorMessage":"${methodName} is not declared writable or has no setter","messagePattern":"(.+?) is not declared writable or has no setter","errorType":"exception","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/f49721c78e195558b40913977c9230f5b7f559d8/packages/jest-jasmine2/src/jasmine/spyRegistry.ts#L103-L139","documentation":"Thrown by `SpyRegistry.spyOn` in spyRegistry.ts:121 when the property descriptor exists but is neither `writable` nor has a `set` accessor (`!(descriptor.writable || descriptor.set)`). jasmine2 replaces the value in place, so a frozen/read-only property without a setter cannot be overwritten.","triggerScenarios":"Spying on a method declared `readonly` in a class, a property on a `Object.freeze`d object, a getter-only property without specifying the access type, or an inherited non-writable property.","commonSituations":"Trying to spy on a getter/setter with the two-arg `spyOn` instead of `spyOnProperty`; class with `readonly` TS fields compiled to non-writable descriptors; frozen config objects.","solutions":["If it is a getter/setter, use `spyOnProperty(obj, 'prop', 'get')` instead of `spyOn`.","If the object is frozen, spy on the prototype or unfreeze/make the property writable before spying.","Refactor the production code so the dependency is injectable rather than a hardcoded readonly property."],"exampleFix":"// before\nspyOn(config, 'timeout') // timeout is a getter\n// after\nspyOnProperty(config, 'timeout', 'get').and.returnValue(5000)","handlingStrategy":"validation","validationCode":"const d = Object.getOwnPropertyDescriptor(obj, methodName);\nif (d && !d.writable && !d.set) { /* use spyOnProperty with access type, or refactor */ }","typeGuard":"const isSpyable = (o: object, m: string): boolean => {\n  const d = Object.getOwnPropertyDescriptor(o, m);\n  return !d || Boolean(d.writable) || Boolean(d.set);\n};","tryCatchPattern":null,"preventionTips":["Use spyOnProperty for getters/setters instead of spyOn.","Avoid freezing/sealing objects you intend to spy on."],"tags":["jasmine2","spy","spy-on","readonly","frozen","descriptor"],"analyzedSha":"f49721c78e195558b40913977c9230f5b7f559d8","analyzedAt":"2026-08-03T20:16:28.571Z","schemaVersion":2}