jestjs/jest · error · Error
has already been spied upon
Error message
${propertyName} has already been spied upon What it means
Thrown by SpyRegistry._spyOnProperty when the property accessor is already a spy and respy is not enabled (spyRegistry.ts:191-198). Same protection as the method variant: a second property-spy would lose the reference to the original getter/setter and break restoration.
Solutions
- Restore between tests: afterEach(() => jest.restoreAllMocks()).
- Consolidate the property spy into a single beforeEach and remove duplicates.
- Opt into respy only if intentional: jasmine.getEnv().allowRespy(true).
- Set restoreMocks:true in the Jest config to reset spies automatically.
Example fix
// before
beforeEach(() => spyOn(config, 'url', 'get'));
it('spies again', () => spyOn(config, 'url', 'get')); // throws
// after
beforeEach(() => jest.spyOn(config, 'url', 'get'));
afterEach(() => jest.restoreAllMocks()); Defensive patterns
Strategy: validation
Validate before calling
afterEach(() => jest.restoreAllMocks());
// before re-spying, restore
if (obj[prop] && (obj[prop] as any).and instanceof Object) {
(obj[prop] as any).and.restore?.();
}
spyOn(obj, prop, 'get'); Type guard
const isPropertySpy = (v: unknown): boolean => v != null && typeof (v as any).and === 'object';
Prevention
- Set restoreMocks:true in Jest config.
- Consolidate property spies into a single beforeEach.
- Use jest.restoreAllMocks in afterEach.
When it happens
Trigger: spyOn(obj, 'p', 'get') called twice in one test, two beforeEach hooks that both spy on the same getter, or a custom utility that re-applies a property spy.
Common situations: Shared setup spies plus per-test spies on getters; refactoring that moved the property spy into a shared helper imported by many specs; multiple files with global setup; mocking a config getter both globally and locally.
Related errors
- : has already been spied upon Usage: spyOn( , )
- Property does not have access type
- is not declared configurable
- : could not find an object to spy upon for Usage: spyOn( , )
- : No property name supplied Usage: spyOn( , )
AI-assisted analysis of jestjs/jest@8e6d128e4a (2026-08-10).
Data as JSON: /api/errors/2d98723865e0dcc4.
Report an issue: GitHub.
Appendix: source
Thrown at packages/jest-jasmine2/src/jasmine/spyRegistry.ts:195
if (!descriptor.configurable) {
throw new Error(
getErrorMsg(`${propertyName} is not declared configurable`),
);
}
if (!descriptor[accessType]) {
throw new Error(
getErrorMsg(
`Property ${propertyName} does not have access type ${accessType}`,
),
);
}
if (obj[propertyName] && isSpy(obj[propertyName])) {
if (this.respy) {
return obj[propertyName];
} else {
throw new Error(
getErrorMsg(`${propertyName} has already been spied upon`),
);
}
}
const originalDescriptor = descriptor;
const spiedProperty = createSpy(propertyName, descriptor[accessType]);
let restoreStrategy;
if (Object.prototype.hasOwnProperty.call(obj, propertyName)) {
restoreStrategy = function () {
Object.defineProperty(obj, propertyName, originalDescriptor);
};
} else {
restoreStrategy = function () {
delete obj[propertyName];
};
}View on GitHub (pinned to 8e6d128e4a)