CodePark/ questions / Mocks, stubs, spies & fakes
START PRACTICINGPRACTICE
Backend question · Backend testing › Mocks, stubs, spies & fakes

Why is jest.mock() hoisted, and why does this CommonJS-compiled test fail with "Cannot access 'mockSend' before initialization"?

import { register } from './users.service';
import { sendWelcome } from './mailer';

const mockSend = jest.fn();
jest.mock('./mailer', () => ({ sendWelcome: mockSend }));

jest.mock must take effect before the module under test loads the real dependency. That is why Jest moves it to the top of the file. This move is called hoisting.

When babel-jest or ts-jest compile your test to CommonJS, every import becomes a require call. Jest's transform then hoists the jest.mock calls above those require calls. So when users.service requires ./mailer, Jest's module registry already holds the mock.

Why the error? The mock factory (the function you pass to jest.mock) runs when ./mailer is first required. That happens while users.service is being imported. At that moment, the line const mockSend = jest.fn() has not run yet. mockSend is still in its temporal dead zone (TDZ: the time between the start of the scope and the line that declares a const or let). Reading a variable in its TDZ throws an error.

Jest lets a factory use outside variables only if their names start with mock. But this rule can't make them ready in time.

Fixes:

  • Create the jest.fn() inside the factory, then get it back with jest.mocked(sendWelcome).
  • Or delay the read with a wrapper: sendWelcome: (...args) => mockSend(...args). Now mockSend is read only when the function is called.
  • In native ESM, jest.mock is not hoisted. Use jest.unstable_mockModule with dynamic imports instead.
  • Hoisting moves jest.mock above the compiled require calls.
  • The factory runs when the module is first required, which happens during the imports.
  • A const declared later in the file is still in its TDZ at that point, so reading it throws.
  • Only variables whose names start with mock are allowed, and they must be read later, not when the factory runs.
  • Create the jest.fn() inside the factory and use jest.mocked() to access it.