{"record":{"id":"0d031b7d39e6dda1","repo":"jestjs/jest","slug":"spies-must-be-created-in-a-before-function-or-a-sp","errorCode":null,"errorMessage":"Spies must be created in a before function or a spec","messagePattern":"Spies must be created in a before function or a spec","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"packages/jest-jasmine2/src/jasmine/Env.ts","lineNumber":365,"sourceCode":"        }\n      };\n\n      this.addReporter = function (reporterToAdd) {\n        reporter.addReporter(reporterToAdd);\n      };\n\n      this.provideFallbackReporter = function (reporterToAdd) {\n        reporter.provideFallbackReporter(reporterToAdd);\n      };\n\n      this.clearReporters = function () {\n        reporter.clearReporters();\n      };\n\n      const spyRegistry = new j$.SpyRegistry({\n        currentSpies() {\n          if (!currentRunnable()) {\n            throw new Error(\n              'Spies must be created in a before function or a spec',\n            );\n          }\n          return runnableResources[currentRunnable().id].spies;\n        },\n      });\n\n      this.allowRespy = function (allow) {\n        spyRegistry.allowRespy(allow);\n      };\n\n      this.spyOn = function (...args) {\n        return spyRegistry.spyOn.apply(spyRegistry, args);\n      };\n\n      const suiteFactory = function (description: Circus.TestNameLike) {\n        const suite = new j$.Suite({\n          id: getNextSuiteId(),","sourceCodeStart":347,"sourceCodeEnd":383,"githubUrl":"https://github.com/jestjs/jest/blob/8e6d128e4a278059ecddecaa97400b04c8ae5fd9/packages/jest-jasmine2/src/jasmine/Env.ts#L347-L383","documentation":"Jasmine's spy registry (spyOn) only tracks spies that belong to a currently-executing runnable (a spec, or a before/beforeAll/afterAll hook). If you call `spyOn(...)` at the top level of a test file — outside any `it`/`beforeEach`/`beforeAll` — there is no current runnable, so the `currentSpies()` callback throws this Error. Spies must be registered within a hook or spec body so they can be cleaned up afterward.","triggerScenarios":"In Env.ts, the SpyRegistry's `currentSpies()` getter (lines 363-370) checks `currentRunnable()`. If null (no spec currently running and no suite executing hooks), it throws. This happens when spyOn is called during module-load/top-level scope, or inside a describe block directly (not inside a before* hook).","commonSituations":"Calling `spyOn(obj, 'method')` directly inside a `describe(...)` body instead of inside `beforeEach`/`beforeAll`; refactoring a test and accidentally moving a spy outside its hook; using a shared helper that calls spyOn at import time.","solutions":["Move the `spyOn(...)` call inside a `beforeEach`, `beforeAll`, or an `it` body.","If the spy must exist for the whole suite, wrap it in `beforeAll(() => { spyOn(...) })`.","Audit test helpers to ensure they are invoked from within a hook or spec, not at module top level."],"exampleFix":"// before\nimport { foo } from './foo';\nspyOn(foo, 'bar'); // top-level → throws\ndescribe('x', () => { it('works', () => {}) });\n// after\ndescribe('x', () => {\n  beforeEach(() => { spyOn(foo, 'bar'); });\n  it('works', () => {});\n});","handlingStrategy":"validation","validationCode":"// In a setupFile, detect top-level spyOn misuse via a lint rule or\n// by wrapping spyOn to check for an active runnable.\n// ESLint rule (eslint-plugin-jest): 'no-standalone-expect' / 'no-spy-at-toplevel'\n// ensures spies are inside hooks or specs.","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Always call spyOn inside beforeEach, beforeAll, or an it body.","Use eslint-plugin-jest rules to catch top-level spy calls.","Keep test helper modules free of side-effecting spyOn calls at import time."],"tags":["spy","jasmine","test-structure","lifecycle","spyon"],"backgroundTag":null,"analyzedSha":"8e6d128e4a278059ecddecaa97400b04c8ae5fd9","analyzedAt":"2026-08-10T18:11:27.960Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}