jestjs/jest · error · Error
<spyOn> : ${methodName} is not declared writable or has no s
Error message
<spyOn> : ${methodName} is not declared writable or has no setter
Usage: spyOn(<object>, <methodName>) What it means
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.
Source
Thrown at packages/jest-jasmine2/src/jasmine/spyRegistry.ts:121
if (obj[methodName] && isSpy(obj[methodName])) {
if (this.respy) {
return obj[methodName];
} else {
throw new Error(
getErrorMsg(`${methodName} has already been spied upon`),
);
}
}
let descriptor;
try {
descriptor = Object.getOwnPropertyDescriptor(obj, methodName);
} catch {
// IE 8 doesn't support `definePropery` on non-DOM nodes
}
if (descriptor && !(descriptor.writable || descriptor.set)) {
throw new Error(
getErrorMsg(
`${methodName} is not declared writable or has no setter`,
),
);
}
const originalMethod = obj[methodName];
const spiedMethod = createSpy(methodName, originalMethod);
let restoreStrategy;
if (Object.prototype.hasOwnProperty.call(obj, methodName)) {
restoreStrategy = function () {
obj[methodName] = originalMethod;
};
} else {
restoreStrategy = function () {
if (!delete obj[methodName]) {
obj[methodName] = originalMethod;View on GitHub (pinned to 8e6d128e4a)
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.
Example fix
// before spyOn(config, 'load'); // config is frozen, read-only // after spyOn(config, 'load', 'get'); // property spy on the getter
Defensive patterns
Strategy: validation
Validate before calling
const d = Object.getOwnPropertyDescriptor(obj, methodName);
if (d && !d.writable && !d.set) {
throw new Error(`${String(methodName)} is read-only; spy via the property accessor`);
}
spyOn(obj, methodName); Type guard
const isWritable = (o: object, k: PropertyKey): boolean => {
const d = Object.getOwnPropertyDescriptor(o, k);
return !!d && (!!d.writable || typeof d.set === 'function');
}; Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- ${propertyName} is not declared configurable
- <spyOn> : could not find an object to spy upon for ${methodN
- <spyOn> : No method name supplied Usage: spyOn(<object>, <me
- <spyOn> : ${methodName}() method does not exist Usage: spyOn
- <spyOn> : ${methodName} has already been spied upon Usage: s
AI-assisted analysis of jestjs/jest@8e6d128e4a (2026-08-10).
Data as JSON: /api/errors/3be1158c144a8be2.
Report an issue: GitHub.