jestjs/jest · error · Error
Spies must be created in a before function or a spec
Error message
Spies must be created in a before function or a spec
What it means
Jasmine's spy registry stores created spies against the currently-executing runnable (a spec or a before/after). The currentSpies() provider in Env.ts:361-369 throws when currentRunnable() is null — meaning spyOn was called at the top level of the file, outside any describe/beforeEach/it. Spies must be tied to a runnable so they can be torn down after it completes.
Source
Thrown at packages/jest-jasmine2/src/jasmine/Env.ts:364
}
};
this.addReporter = function (reporterToAdd) {
reporter.addReporter(reporterToAdd);
};
this.provideFallbackReporter = function (reporterToAdd) {
reporter.provideFallbackReporter(reporterToAdd);
};
this.clearReporters = function () {
reporter.clearReporters();
};
const spyRegistry = new j$.SpyRegistry({
currentSpies() {
if (!currentRunnable()) {
throw new Error(
'Spies must be created in a before function or a spec',
);
}
return runnableResources[currentRunnable().id].spies;
},
});
this.allowRespy = function (allow) {
spyRegistry.allowRespy(allow);
};
this.spyOn = function (...args) {
return spyRegistry.spyOn.apply(spyRegistry, args);
};
const suiteFactory = function (description: Circus.TestNameLike) {
const suite = new j$.Suite({
id: getNextSuiteId(),View on GitHub (pinned to f49721c78e)
Solutions
- Move the spyOn call inside beforeEach/beforeAll or inside the it() body where a runnable is active.
- If the spy is needed across setup, define it inside beforeAll and reference the returned spy handle in tests.
- Avoid module-level side effects that call spyOn during import.
Example fix
// before — spy created at top level (no runnable)
const spy = jest.spyOn(math, 'add');
describe('math', () => { it('adds', () => { ... }); });
// after — spy created inside a runnable
describe('math', () => {
let spy;
beforeEach(() => { spy = jest.spyOn(math, 'add'); });
afterEach(() => { spy.mockRestore(); });
it('adds', () => { ... });
}); Defensive patterns
Strategy: validation
Validate before calling
// Static check: ensure spyOn/jest.spyOn calls live inside a runnable body.
// Simple heuristic: the call's nearest enclosing function is it/test/before*/after*.
// (Enforce via an ESLint custom rule or a pre-test scan.)
function spyNotInRunnable(source: string): boolean {
// returns true if a jest.spyOn/spyOn call appears at describe-body top level
return /describe\([^)]*\)\s*=>\s*{[^}]*spyOn/.test(source);
} Prevention
- Always create spies inside beforeEach/beforeAll/it, never at module or describe top level.
- Restore spies in afterEach to avoid leakage that encourages re-spying at the wrong scope.
- Avoid module side effects that call spyOn during import.
When it happens
Trigger: Calling `jest.spyOn(...)` (or jasmine spyOn) directly in the module body of a test file or in a describe block's synchronous body (not inside a before* or it). currentRunnable() returns null because no spec or before/after is executing at that moment, so the provider at Env.ts:362 throws.
Common situations: Refactoring a test and moving a spyOn out of beforeEach to the top level; calling spyOn during module evaluation (e.g. in a helper imported for side effects); forgetting to wrap setup in beforeEach; spy creation triggered by an import statement.
Related errors
- Missing second argument. It must be a callback function.
- Invalid second argument, ${specDefinitions}. It must be a ca
- describe does not expect any arguments
- Missing second argument. It must be a callback function. Per
- Invalid second argument, ${fn}. It must be a callback functi
AI-assisted analysis of jestjs/jest@f49721c78e (2026-08-03).
Data as JSON: /data/errors/0d031b7d39e6dda1.json.
Report an issue: GitHub.