TypeScript

Fix: ReferenceError: jest is not defined (ESM tests)

Access Jest's mocking object in native ESM tests without confusing it with the test, expect and global names that remain available by default.

Mahdi Ben RhoumaMahdi Ben Rhouma
Updated
6 min read

TL;DR#

ReferenceError: jest is not defined on jest.fn() almost always means your project runs Jest under native ES modules ("type": "module" in package.json, tests started with --experimental-vm-modules) — and native ESM requires explicit access to the jest object. Add import {jest} from '@jest/globals' to the top of the file, or expose it globally once from a setup file.

The error#

A test that mocks fetch throws before it gets anywhere near an assertion:

text
ReferenceError: jest is not defined
 
    at Object.<anonymous> (app.test.js:7:20)

The line it points at is a completely ordinary mock:

app.test.js (before the fix)
javascript
test("Get total number of people", async () => {
    global.fetch = jest.fn(() => Promise.resolve({
            json: () => Promise.resolve({count: 15})
        })
    )
 
    expect(await getTotalNumPeople()).toBe(15);
});

This exact symptom was reported on Stack Overflow by a developer who had just switched a plain Node project to ES modules, adding "type": "module" to package.json and following the usual guide for running Jest under --experimental-vm-modules. The test worked with CommonJS the day before; nothing in the test file itself changed, only the module system around it. The question also notes a second, related symptom: commenting out the jest.fn() mock doesn't make the test pass — it just trades one ReferenceError for another, this time complaining that global itself is not defined, since the mock also reaches for global.fetch. That reported symptom needs a separate environment diagnosis: native ESM alone does not remove Node's global object.

Why it happens#

With the default injectGlobals setting, Jest exposes test, describe and expect globally. In CommonJS test files, Jest also supplies the jest object through its module wrapper.

Native ES modules don't go through that wrapper. When package.json has "type": "module" and Jest runs the file as a real ES module (via Node's --experimental-vm-modules flag), Jest's own documentation is explicit that the jest object needs explicit access in ESM — import it from @jest/globals or use import.meta.jest. This does not remove test, describe or expect when Jest's default injectGlobals setting is enabled. Node's global remains available too; it is distinct from the CommonJS module-local names such as require and module. If global is missing, inspect the actual execution environment instead of treating it as an automatic consequence of ESM.

Fix#

1. Import jest explicitly in the test file#

The minimal fix, and the one Jest's own ESM guide recommends first, is a single import at the top of the affected file:

app.test.js
javascript
import { jest } from '@jest/globals';
import { getTotalNumPeople } from './app.js';
 
test('Get total number of people', async () => {
  global.fetch = jest.fn(() =>
    Promise.resolve({
      json: () => Promise.resolve({ count: 15 }),
    })
  );
 
  expect(await getTotalNumPeople()).toBe(15);
});

If your linter needs explicit names, use import { jest, expect, test } from '@jest/globals'. Importing jest alone does not also import the other names.

2. Or expose jest globally once, for every test file#

If you don't want the import at the top of every file, add a setup file that runs before your suite and attaches jest to global:

test-setup.js
javascript
import { jest } from '@jest/globals';
 
global.jest = jest;
package.json
json
{
  "jest": {
    "setupFiles": ["<rootDir>/test-setup.js"]
  }
}

Every test file in the project can then use jest.fn() without its own import, the same way it worked before switching to ESM.

3. Confirm the ESM run itself is configured#

ReferenceError: jest is not defined only appears once the file is actually running as an ES module — if Jest instead fails immediately with SyntaxError: Cannot use import statement outside a module, the ESM runner isn't wired up yet. Jest needs Node started with the experimental VM modules flag: A Vite project that leaves Jest on CommonJS fails differently, with SyntaxError: Cannot use 'import.meta' outside a module wherever the source reads import.meta.env, and the import.meta fix for Jest in a Vite project covers mapping those reads to one stand-in module or testing with Vitest instead.

package.json scripts
bash
"test": "node --experimental-vm-modules node_modules/jest/bin/jest.js"

or, equivalently, NODE_OPTIONS=--experimental-vm-modules npx jest. On Windows, set the variable with cross-env rather than inline, since NODE_OPTIONS=... command isn't valid syntax in cmd.exe or PowerShell.

Check your own module error#

ReferenceError: jest is not defined is one specific symptom of a broader family of ESM/CommonJS mismatches — require() of ES Module not supported, exports is not defined in ES module scope, and a handful of others all trace back to the same root cause: some part of the toolchain expected CommonJS and got an ES module, or vice versa. If you're not sure which of those you're actually looking at, paste the exact error below:

Runs entirely in your browser — nothing you paste is sent anywhere. Tokens and passwords that appear in paths or URLs are masked in the output.

Optional: package.json, file extension, Node.js version (sharper advice)
Try:

Verify the fix#

Re-run the failing test file:

bash
npm test

jest.fn() should now return a mock function instead of throwing, and the assertion runs normally through to completion. If you added the global setup file instead of the per-file import, check a different test file that never imports jest directly — it should also see jest.fn without a ReferenceError, which confirms the setup file actually loaded before the suite rather than the fix only working by coincidence in the one file you edited.

Also re-run your full suite, not just the one file you edited. If some test files still throw ReferenceError: jest is not defined while others don't, double-check that every affected file is actually covered by the same Jest configuration — a monorepo with more than one jest.config.js, or a testPathIgnorePatterns override in one workspace, can leave the setup file wired to only part of the project.

One email a month — the latest explained errors

What the site published that month — mostly Next.js, Supabase and Node errors, explained. You confirm by email; unsubscribe any time in one click.

The import.meta.jest alternative#

Jest also exposes the same object through import.meta.jest inside an ESM test file, without any import statement:

javascript
import.meta.jest.useFakeTimers();

It's equivalent to importing from @jest/globals — jest === import.meta.jest is true in a Jest ESM run — so which one you use is a style choice, not a functional difference. The explicit import tends to read better in a file that also imports expect and describe the same way.

If your ESM migration instead trips on the bundler side — Vite or Rollup rejecting an import rather than Jest rejecting a global — the failure mode and the fix are different; see Fix Cannot find module '@vitejs/plugin-react' in Vite for that class of error, and Fix ESLint 'import/extensions' Definition Not Found if the complaint is coming from your linter instead of your test runner. And if TypeScript itself is the one complaining — a red squiggle under jest in your editor even after the import compiles and runs — that's a missing @types/jest or a tsconfig.json types array that doesn't list it, a separate class of error covered in Fix TS7016: Could Not Find Declaration File for Module.

This flag is still labelled experimental in Jest's own docs years after ESM support landed — expect the odd rough edge like this one until Jest ships first-class ESM support without the flag.

Further reading: Cannot access 'Player' before initialization: ESM cycle.

Frequently Asked Questions

|

Have more questions? Contact us

Still stuck? Paste your error.

Paste the error message or stack trace — we check it against every fix and tool on the site.

Matching happens in your browser. When you search, a cleaned error signature is sent so we can spot errors we do not cover yet — never the raw text, your IP address or your e-mail. Do not paste passwords or keys. Details

Written by

Mahdi Ben Rhouma
Mahdi Ben Rhouma

Full-Stack Dev — Next.js & Supabase

Solo developer building SaaS products with Next.js and Supabase. Writing about production patterns the official docs skip.

Remote