{"id":"a0b03fe50c9926e5","repo":"jestjs/jest","slug":"jasmine-spies-would-overwrite-the-and-and-calls","errorCode":null,"errorMessage":"Jasmine spies would overwrite the 'and' and 'calls' properties on the object being spied upon","messagePattern":"Jasmine spies would overwrite the 'and' and 'calls' properties on the object being spied upon","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/jest-jasmine2/src/jasmine/createSpy.ts","lineNumber":65,"sourceCode":"    },\n  });\n  const callTracker = new CallTracker();\n  const spy: Spy = function (...args) {\n    const callData: Context = {\n      object: this,\n      args: Array.prototype.slice.apply(arguments),\n    };\n\n    callTracker.track(callData);\n    const returnValue = spyStrategy.exec.apply(this, args);\n    callData.returnValue = returnValue;\n\n    return returnValue;\n  };\n\n  for (const prop in originalFn) {\n    if (prop === 'and' || prop === 'calls') {\n      throw new Error(\n        \"Jasmine spies would overwrite the 'and' and 'calls' properties \" +\n          'on the object being spied upon',\n      );\n    }\n\n    spy[prop] = originalFn[prop];\n  }\n\n  spy.and = spyStrategy;\n  spy.calls = callTracker;\n\n  return spy;\n}\n\nexport default createSpy;\n","sourceCodeStart":47,"sourceCodeEnd":81,"githubUrl":"https://github.com/jestjs/jest/blob/f49721c78e195558b40913977c9230f5b7f559d8/packages/jest-jasmine2/src/jasmine/createSpy.ts#L47-L81","documentation":"createSpy() copies enumerable properties from the original function onto the spy, then assigns the spy's own `and` (SpyStrategy) and `calls` (CallTracker) properties. If the original function already has an `and` or `calls` own property, copying would clobber them — so createSpy throws to avoid silently overwriting user data and breaking the spy API.","triggerScenarios":"Calling createSpy(name, originalFn) where originalFn has an own property named 'and' or 'calls' (createSpy.ts:63-69). This happens when spying on a function that was already spied upon, or a function decorated with its own and/calls bookkeeping.","commonSituations":"Re-spying an already-spied function ( Jasmine spy already installed .and/.calls); spying on a method that another library decorated with and/calls; double jest.spyOn on the same target without restore; spying on a function returned by another spy factory.","solutions":["Restore the previous spy (spy.mockRestore() / mockClear()) before spying again so the function no longer carries and/calls.","Spy on a fresh copy of the function instead of an already-decorated one.","Audit for double-spying: search for repeated spyOn on the same object/method across setup files.","If you control the original function, rename its and/calls properties to avoid the reserved names."],"exampleFix":"// before — re-spying a function that already has .and/.calls\nconst s1 = jest.spyOn(obj, 'fn');\nconst s2 = jest.spyOn(obj, 'fn'); // createSpy sees existing .and/.calls -> throws\n\n// after — restore before re-spying\nconst s1 = jest.spyOn(obj, 'fn');\ns1.mockRestore();\nconst s2 = jest.spyOn(obj, 'fn');","handlingStrategy":"type-guard","validationCode":"function canSpy(fn: { and?: unknown; calls?: unknown }): boolean {\n  return !('and' in fn) && !('calls' in fn);\n}\nif (!canSpy(originalFn)) {\n  throw new Error('target already has and/calls properties; restore the previous spy first');\n}","typeGuard":"function isSpyable(fn: object): boolean {\n  const own = Object.getOwnPropertyNames(fn);\n  return !own.includes('and') && !own.includes('calls');\n}","tryCatchPattern":null,"preventionTips":["Always restore spies (mockRestore/mockClear) before re-spying the same target.","Avoid spying on functions that other libraries have decorated with and/calls.","Centralize spy setup in beforeEach with matching afterEach restore to prevent double-spying."],"tags":["jasmine2","spy","createspy","property-collision"],"analyzedSha":"f49721c78e195558b40913977c9230f5b7f559d8","analyzedAt":"2026-08-03T20:16:28.571Z","schemaVersion":2}