Fix: jest.mock() Not Working — Module Not Being Replaced in Tests
Quick Answer
How to fix jest.mock() not intercepting module calls — why mocks are ignored, how to correctly mock ES modules, default exports, named exports, and fix hoisting issues in Jest tests.
The Error
You set up a mock with jest.mock() but the real module runs anyway:
jest.mock('./api/users');
import { getUsers } from './api/users';
test('loads users', async () => {
getUsers.mockResolvedValue([{ id: 1, name: 'Alice' }]);
// ...but getUsers still calls the real API
});Or the mock function is not recognized:
TypeError: getUsers.mockResolvedValue is not a functionOr a default export mock does not work:
jest.mock('./logger');
import logger from './logger';
logger.info('test'); // Still calls the real loggerOr with ES modules:
SyntaxError: Cannot use import statement inside a moduleWhy This Happens
jest.mock() works by intercepting require() calls at the module registry level. Several patterns break this:
- Wrong import order —
jest.mock()must be called before the module is imported. Jest hoistsjest.mock()calls to the top of the file automatically, but only forrequire()— with ES moduleimportsyntax, the hoisting may not work as expected. - Mocking the wrong path — the path passed to
jest.mock()must match exactly how the module is imported in the code under test. - Default export not mocked correctly —
jest.mock('./module')auto-mocks named exports but requires explicit handling for default exports. - ES module mode (
"type": "module") — Jest’s module mocking works differently with native ES modules. - Module is imported transitively — the module was already imported by another module before your mock ran.
- Factory function not returning the mock shape — when using
jest.mock('module', factory), the factory must return an object with the same shape as the module.
Fix 1: Ensure jest.mock() Is Hoisted Correctly
jest.mock() calls are hoisted to the top of the file by Babel’s babel-jest transform. However, variables defined in the test file are not hoisted — only the jest.mock() call itself is:
Broken — using a variable in jest.mock() that is not yet defined:
const mockGetUsers = jest.fn();
// This line is hoisted to the top, but mockGetUsers is NOT hoisted with it
jest.mock('./api/users', () => ({
getUsers: mockGetUsers, // undefined at hoist time — ReferenceError
}));Fixed — use jest.fn() inline or prefix variable with mock:
// Variables starting with 'mock' are hoisted along with jest.mock()
const mockGetUsers = jest.fn();
jest.mock('./api/users', () => ({
getUsers: mockGetUsers, // Works — babel-jest hoists 'mock*' variables
}));
// Or inline — always works:
jest.mock('./api/users', () => ({
getUsers: jest.fn(),
}));Then access the mock after import:
import { getUsers } from './api/users';
jest.mock('./api/users', () => ({
getUsers: jest.fn(),
}));
test('calls getUsers', async () => {
getUsers.mockResolvedValue([{ id: 1, name: 'Alice' }]);
const result = await loadDashboard();
expect(getUsers).toHaveBeenCalledOnce();
expect(result.users).toHaveLength(1);
});
afterEach(() => {
jest.clearAllMocks(); // Reset call counts and return values between tests
});Fix 2: Mock Default Exports Correctly
Default exports require explicit handling in the factory function:
Broken — does not mock the default export:
jest.mock('./logger');
import logger from './logger';
logger.info('test'); // logger is {} — the real logger is not replacedFixed — return a default property in the factory:
jest.mock('./logger', () => ({
default: {
info: jest.fn(),
warn: jest.fn(),
error: jest.fn(),
},
}));
import logger from './logger';
test('logs a message', () => {
myFunction(); // internally calls logger.info(...)
expect(logger.info).toHaveBeenCalledWith('expected message');
});If the module has both default and named exports:
// Original module: export default logger; export { format };
jest.mock('./logger', () => ({
__esModule: true, // Required when mocking ES modules with default exports
default: {
info: jest.fn(),
warn: jest.fn(),
},
format: jest.fn((msg) => msg),
}));Note: The
__esModule: trueflag tells Jest to treat the mock as an ES module, enabling correct default export handling. Without it,import logger from './logger'receives the entire mock object, not its.defaultproperty.
Fix 3: Fix Path Mismatch
jest.mock() intercepts the module at the path used by the code under test — not the path you use in the test file:
Example — the code under test imports from a different path:
// src/services/userService.js
import { getUsers } from '../api/users'; // Imports from '../api/users'
// Your test file:
import userService from '../services/userService';
// Wrong — mocking './api/users' but the code imports '../api/users' relative to itself
jest.mock('./api/users');
// Correct — mock the path as seen from the test file's perspective
jest.mock('../api/users');The path in jest.mock() must resolve to the same file as the import in the code under test — use the path relative to the test file, or use an absolute alias.
With path aliases (TypeScript/webpack):
// tsconfig.json defines: "@api/*" → "src/api/*"
// Code under test uses: import { getUsers } from '@api/users';
// In your Jest config (jest.config.js), map the alias:
moduleNameMapper: {
'^@api/(.*)$': '<rootDir>/src/api/$1',
},
// In the test — use the alias:
jest.mock('@api/users', () => ({
getUsers: jest.fn(),
}));Fix 4: Fix ES Module Mocking
If your project uses native ES modules ("type": "module" in package.json), jest.mock() works differently:
For projects using Babel to transform ES modules (most common):
Ensure babel-jest is configured and @babel/preset-env transforms modules to CommonJS:
// babel.config.js
module.exports = {
presets: [
['@babel/preset-env', { targets: { node: 'current' }, modules: 'commonjs' }],
'@babel/preset-typescript',
],
};// jest.config.js
module.exports = {
transform: {
'^.+\\.[tj]sx?$': 'babel-jest',
},
// Do NOT set "extensionsToTreatAsEsm" when using Babel for transformation
};For native ES modules without Babel (experimental):
// jest.config.js
export default {
extensionsToTreatAsEsm: ['.ts'],
transform: {
'^.+\\.tsx?$': ['ts-jest', { useESM: true }],
},
};// In your test file — use jest.unstable_mockModule for native ESM
import { jest } from '@jest/globals';
// Must be called before dynamic import
jest.unstable_mockModule('./api/users', () => ({
getUsers: jest.fn().mockResolvedValue([]),
}));
// Dynamic import AFTER the mock setup
const { getUsers } = await import('./api/users');
const { loadDashboard } = await import('./dashboard');Fix 5: Mock Node.js Built-in Modules
Mocking built-in modules like fs, path, or crypto requires explicit module mapping:
// Mock the entire 'fs' module
jest.mock('fs');
import fs from 'fs';
test('reads a file', () => {
fs.readFileSync.mockReturnValue('file contents');
const result = readConfig('./config.json');
expect(result).toBe('file contents');
expect(fs.readFileSync).toHaveBeenCalledWith('./config.json', 'utf8');
});Mock only specific methods with jest.spyOn:
import fs from 'fs';
test('reads a file', () => {
const spy = jest.spyOn(fs, 'readFileSync').mockReturnValue('file contents');
const result = readConfig('./config.json');
expect(result).toBe('file contents');
spy.mockRestore(); // Clean up the spy
});Fix 6: Use jest.spyOn for Partial Mocks
When you only want to mock one method of a module while keeping the rest real:
import * as userApi from './api/users';
test('calls getUsers with correct params', async () => {
// Mock only getUsers — all other exports remain real
const spy = jest.spyOn(userApi, 'getUsers').mockResolvedValue([
{ id: 1, name: 'Alice' },
]);
const result = await loadDashboard({ active: true });
expect(spy).toHaveBeenCalledWith({ active: true });
expect(result).toHaveLength(1);
spy.mockRestore(); // Restore original implementation
});Common Mistake: jest.spyOn requires the module to be imported as a namespace (import * as module). It does not work with destructured imports because it needs to replace a property on the module object:
// Wrong — cannot spy on a destructured import
import { getUsers } from './api/users';
jest.spyOn({ getUsers }, 'getUsers'); // Does not affect the real getUsers
// Correct
import * as userApi from './api/users';
jest.spyOn(userApi, 'getUsers').mockResolvedValue([]);Fix 7: Reset and Restore Mocks Between Tests
Mocks that bleed between tests cause intermittent test failures:
// jest.config.js — auto-reset after each test
module.exports = {
clearMocks: true, // Clears mock.calls, mock.instances, mock.results
resetMocks: true, // Resets mock implementation (like mockReturnValue)
restoreMocks: true, // Restores spies created with jest.spyOn
};Or manually in each test file:
afterEach(() => {
jest.clearAllMocks(); // Clear call history
});
afterAll(() => {
jest.restoreAllMocks(); // Restore spies
});Reset a specific mock’s implementation:
const mockGetUsers = jest.fn();
beforeEach(() => {
mockGetUsers.mockReset(); // Clears calls AND removes mockReturnValue/mockImplementation
});
test('returns empty list by default', async () => {
mockGetUsers.mockResolvedValue([]);
// ...
});
test('returns users when available', async () => {
mockGetUsers.mockResolvedValue([{ id: 1, name: 'Alice' }]);
// ...
});Still Not Working?
Check that the module factory returns the correct shape. If jest.mock('./module', () => { ... }), the factory’s return value replaces the entire module. Missing a named export means the code under test receives undefined for that export.
Verify Jest is transforming the file. If babel-jest is not processing a file (e.g., it is in transformIgnorePatterns), jest.mock() hoisting does not work:
// jest.config.js — ensure node_modules that use ESM are transformed
transformIgnorePatterns: [
'/node_modules/(?!(some-esm-package|another-package)/)',
],Use jest.requireActual to partially use the real module:
jest.mock('./config', () => ({
...jest.requireActual('./config'), // Keep all real exports
API_URL: 'http://localhost:3000', // Override only this one
}));Debug which module is actually being imported:
test('debug mock', () => {
const mod = jest.requireMock('./api/users');
console.log('Mock module:', mod);
console.log('Is mock function:', jest.isMockFunction(mod.getUsers));
});For related testing issues, see Fix: Jest Test Timeout and Fix: Jest Cannot Find Module.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Vitest Setup Not Working (setupFiles, Mocks, and Global Config Issues)
How to fix Vitest configuration not taking effect — why setupFiles don't run, globals are undefined, mocks don't work, and how to configure Vitest correctly for React, Vue, and Node.js projects.
Fix: TypeScript Decorators Not Working (experimentalDecorators)
How to fix TypeScript decorators not applying — experimentalDecorators not enabled, emitDecoratorMetadata missing, reflect-metadata not imported, and decorator ordering issues.
Fix: Next.js Middleware Not Running (middleware.ts Not Intercepting Requests)
How to fix Next.js middleware not executing — wrong file location, matcher config errors, middleware not intercepting API routes, and how to debug middleware execution in Next.js 13 and 14.
Fix: TypeScript isolatedModules Errors (const enum, type-only imports)
How to fix TypeScript isolatedModules errors — why const enum fails with Babel and Vite, how to replace const enum, fix re-exported types, and configure isolatedModules correctly for your build tool.