{"id":"2d98723865e0dcc4","repo":"jestjs/jest","slug":"propertyname-has-already-been-spied-upon","errorCode":null,"errorMessage":"${propertyName} has already been spied upon","messagePattern":"(.+?) has already been spied upon","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/jest-jasmine2/src/jasmine/spyRegistry.ts","lineNumber":195,"sourceCode":"      if (!descriptor.configurable) {\n        throw new Error(\n          getErrorMsg(`${propertyName} is not declared configurable`),\n        );\n      }\n\n      if (!descriptor[accessType]) {\n        throw new Error(\n          getErrorMsg(\n            `Property ${propertyName} does not have access type ${accessType}`,\n          ),\n        );\n      }\n\n      if (obj[propertyName] && isSpy(obj[propertyName])) {\n        if (this.respy) {\n          return obj[propertyName];\n        } else {\n          throw new Error(\n            getErrorMsg(`${propertyName} has already been spied upon`),\n          );\n        }\n      }\n\n      const originalDescriptor = descriptor;\n      const spiedProperty = createSpy(propertyName, descriptor[accessType]);\n      let restoreStrategy;\n\n      if (Object.prototype.hasOwnProperty.call(obj, propertyName)) {\n        restoreStrategy = function () {\n          Object.defineProperty(obj, propertyName, originalDescriptor);\n        };\n      } else {\n        restoreStrategy = function () {\n          delete obj[propertyName];\n        };\n      }","sourceCodeStart":177,"sourceCodeEnd":213,"githubUrl":"https://github.com/jestjs/jest/blob/f49721c78e195558b40913977c9230f5b7f559d8/packages/jest-jasmine2/src/jasmine/spyRegistry.ts#L177-L213","documentation":"Thrown by `SpyRegistry._spyOnProperty` in spyRegistry.ts:195 when the property value is already a jasmine spy and `respy` is not enabled. Same double-spy protection as the method variant, applied to property accessors.","triggerScenarios":"Calling `spyOnProperty(obj, 'p', 'get')` twice in the same test, or once in `beforeEach` and again in the test body.","commonSituations":"Shared `beforeEach` plus a per-test spy; helper that spies unconditionally; module-level object whose spies are not restored between tests.","solutions":["Spy once — remove the duplicate `spyOnProperty` call.","Enable respy via `jasmine.getEnv().allowRespy(true)` if re-spying is intentional.","Ensure isolation so the prior spy is restored before the next attempt."],"exampleFix":"// before\nbeforeEach(() => spyOnProperty(cfg, 'env', 'get'));\nit('x', () => { spyOnProperty(cfg, 'env', 'get'); /* throws */ });\n// after\nbeforeEach(() => spyOnProperty(cfg, 'env', 'get'));\nit('x', () => { /* reuse the existing spy */ });","handlingStrategy":"validation","validationCode":"const cur = obj[propertyName];\nif (cur && cur.and && cur.calls) { /* already a spy */ }\nelse spyOnProperty(obj, propertyName, 'get');","typeGuard":"const isSpy = (v: any): boolean =>\n  v != null && v.and != null && typeof v.and === 'object' && v.calls != null;","tryCatchPattern":null,"preventionTips":["Register property spies in a single beforeEach.","Enable allowRespy(true) when re-spying is intentional."],"tags":["jasmine2","spy","spy-on-property","double-spy","respy"],"analyzedSha":"f49721c78e195558b40913977c9230f5b7f559d8","analyzedAt":"2026-08-03T20:16:28.571Z","schemaVersion":2}