{"id":"13d68d571d3c47e0","repo":"jestjs/jest","slug":"methodname-has-already-been-spied-upon","errorCode":null,"errorMessage":"${methodName} 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":107,"sourceCode":"          getErrorMsg(\n            `could not find an object to spy upon for ${methodName}()`,\n          ),\n        );\n      }\n\n      if (methodName === void 0) {\n        throw new Error(getErrorMsg('No method name supplied'));\n      }\n\n      if (obj[methodName] === void 0) {\n        throw new Error(getErrorMsg(`${methodName}() method does not exist`));\n      }\n\n      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        );","sourceCodeStart":89,"sourceCodeEnd":125,"githubUrl":"https://github.com/jestjs/jest/blob/f49721c78e195558b40913977c9230f5b7f559d8/packages/jest-jasmine2/src/jasmine/spyRegistry.ts#L89-L125","documentation":"Thrown by `SpyRegistry.spyOn` in spyRegistry.ts:107 when the method is already a jasmine spy (`isSpy` returns true) and `respy` is not enabled. jasmine2 forbids double-spying by default to avoid losing the original reference and producing confusing call counts.","triggerScenarios":"Calling `spyOn(obj, 'm')` twice in the same test, or in a `beforeEach` that runs for a test which also spies; sharing the object across tests without letting jasmine restore it.","commonSituations":"A shared `beforeEach` spies and an individual test spies again; helper that spies unconditionally and is called from multiple setup hooks; tests not isolated because the object is module-level and spies accumulate.","solutions":["Spy once — remove the duplicate `spyOn` call.","If you genuinely need to re-spy, enable respy via `jasmine.getEnv().allowRespy(true)` (or `spyOnProperty` equivalent) before the second spy.","Ensure tests are isolated so the previous spy is restored before the next spy attempt."],"exampleFix":"// before\nbeforeEach(() => spyOn(db, 'query'));\nit('x', () => { spyOn(db, 'query'); /* throws */ });\n// after\nbeforeEach(() => spyOn(db, 'query'));\nit('x', () => { db.query.mockImplementation(() => 1); });","handlingStrategy":"validation","validationCode":"if (obj[methodName] && obj[methodName].and instanceof Object) { /* already a spy, skip or respy */ }\nelse spyOn(obj, methodName);","typeGuard":"const isSpy = (v: any): boolean =>\n  v != null && v.and != null && typeof v.and === 'object' && v.calls != null;","tryCatchPattern":null,"preventionTips":["Centralize spies in one beforeEach to avoid duplicate registrations.","Enable allowRespy(true) only when re-spying is intentional."],"tags":["jasmine2","spy","spy-on","double-spy","respy"],"analyzedSha":"f49721c78e195558b40913977c9230f5b7f559d8","analyzedAt":"2026-08-03T20:16:28.571Z","schemaVersion":2}