Jest · Unit tests that survive refactors
How does jest.mock work and why does hoisting matter?
Short answer
jest.mock replaces a module's exports for the test file and is hoisted above imports. Its factory cannot close over ordinary outer variables unless they are named with a mock prefix.
When a module is imported, Jest resolves it through its own registry. jest.mock registers a replacement for that path before the module is required. Babel and ts-jest hoist the call to the top of the file so the replacement exists by the time the import runs.
jest.mock("./mailer", () => ({
sendEmail: jest.fn().mockResolvedValue({ ok: true }),
}));
import { sendEmail } from "./mailer";
import { notifyUser } from "./notify";
test("sends one email", async () => {
await notifyUser("asha@example.com");
expect(sendEmail).toHaveBeenCalledTimes(1);
});The hoisting rule
- The
jest.mockcall is moved above imports by the transform - A factory referencing a top-level variable can fail with a module initialization error
- Variables prefixed with
mockare allowed by the hoist guard - Use
jest.requireActualinside the factory to keep real exports - Use
jest.requireMockto read the replacement
jest.mock("./mailer", () => {
const actual = jest.requireActual("./mailer");
return {
...actual,
sendEmail: jest.fn(),
};
});jest.mock is file-scoped. It applies to the module registry used by that test file, not to other files. If you need a different mock per test, import the mocked module and change the implementation with mockImplementation, or call jest.resetModules() and re-require carefully.
Follow-ups you should expect
Interviewers use these to check whether the first answer was understanding or memorisation.
- 1Why does a jest.mock factory fail when it references an outer variable?
- 2How do you partially mock a module?
- 3Does jest.mock affect other test files?
- 4When is jest.resetModules necessary?
Say it in the interview
jest.mock replaces a module's exports for the current test file. The transform hoists it above imports so the replacement is registered before the import runs. That hoisting is why a factory cannot freely reference outer variables; the prefix mock is the escape hatch. For partial mocks I use jest.requireActual and spread the real exports, then override one function.
Use this as a model, not a script. Saying it in your own words is what makes it survive follow-up pressure.
Related questions
Reading is step one
Now say it out loud.
Understanding an answer and delivering it under pressure are different skills. Rehearse this topic in a live Jest mock interview and get scored feedback on depth, clarity and confidence.