Fix: Jest Module Mock Not Working — jest.mock() Has No Effect
Quick Answer
How to fix Jest module mocks not working — hoisting behavior, ES module mocks, factory functions, mockReturnValue vs implementation, and clearing mocks between tests.
The Problem
jest.mock() is called but the original module is still used:
import { fetchUser } from './api';
jest.mock('./api');
test('loads user', async () => {
fetchUser.mockResolvedValue({ name: 'Alice' });
// fetchUser still makes a real HTTP request — mock has no effect
});Or a mock is set up but returns undefined instead of the mocked value:
jest.mock('./database', () => ({
query: jest.fn(),
}));
// In the test:
const { query } = require('./database');
query.mockReturnValue([{ id: 1 }]); // Set up mock
const result = await userService.getUsers();
// result is empty — query returned undefined, not [{ id: 1 }]Or TypeScript shows an error on the mock:
Property 'mockResolvedValue' does not exist on type '() => Promise<User>'
ts(2339)Why This Happens
Jest’s mock system has several non-obvious behaviors:
- Hoisting —
jest.mock()calls are automatically hoisted to the top of the file by Babel’sbabel-jesttransform. If the module is imported before the mock is applied (or hoisting fails), the real module is used. - ES modules bypass hoisting — native ESM doesn’t support
jest.mock()hoisting because ES imports are static and resolved before any code runs. Jest’s hoisting only works with CommonJS or Babel-transpiled TypeScript/ESM. - Factory function scope — the factory function passed to
jest.mock()runs in a sandboxed scope. Variables from the outer scope (includingjest.fn()created outside) aren’t accessible without special handling. - Module caching — Jest caches modules per test file. If a module is required before the mock is set up (in module initialization code), the cached version is used.
- Wrong import style — if the module under test uses
require()internally but you mock it with ESM import syntax, or vice versa, the mock may not intercept the call.
Fix 1: Ensure jest.mock() Is at the Top Level
jest.mock() must be at the top level of the file (not inside functions, beforeEach, or describe blocks) for Babel hoisting to work:
// WRONG — inside describe() — hoisting won't move this above imports
describe('UserService', () => {
jest.mock('./api'); // ← Runs after imports — mock not applied
// ...
});
// WRONG — inside beforeEach — too late
beforeEach(() => {
jest.mock('./api'); // ← Not hoisted, runs at runtime after module is already imported
});
// CORRECT — at top level, outside all blocks
jest.mock('./api'); // ← Babel hoists this above import statements
import { fetchUser } from './api';
describe('UserService', () => {
test('...', () => { /* ... */ });
});Babel transforms this to:
// What Babel actually generates (simplified):
jest.mock('./api');
const api_1 = require('./api');
// Mock is applied before the requireFix 2: Use jest.fn() in the Factory Function
When using a factory function with jest.mock(), create jest.fn() instances inside the factory — not outside it:
// WRONG — referencing outer variable in factory
const mockQuery = jest.fn(); // Created in outer scope
jest.mock('./database', () => ({
query: mockQuery, // ← Can't access outer scope from factory — ReferenceError
}));// CORRECT — create jest.fn() inside the factory
jest.mock('./database', () => ({
query: jest.fn(),
connect: jest.fn().mockResolvedValue(true),
disconnect: jest.fn(),
}));
// Access the mock in tests
const { query } = require('./database');
test('getUsers calls query', async () => {
query.mockResolvedValue([{ id: 1, name: 'Alice' }]);
const users = await userService.getUsers();
expect(query).toHaveBeenCalledWith('SELECT * FROM users');
expect(users).toHaveLength(1);
});If you need a reference to the mock function outside the factory, use jest.mocked() or a require after the mock:
jest.mock('./database', () => ({
query: jest.fn(),
}));
// Import after jest.mock() — gets the mocked version
import { query } from './database';
// OR use require() in tests
const db = require('./database');
test('...', () => {
db.query.mockReturnValue([]);
});Fix 3: Mock Default Exports Correctly
Default exports and named exports require different mock syntax:
// Module: utils/logger.js
export default function log(message) {
console.log(message);
}
export const warn = (msg) => console.warn(msg);// Mocking default export
jest.mock('./utils/logger', () => {
return {
default: jest.fn(), // ← 'default' key for default export
warn: jest.fn(), // Named export
__esModule: true, // ← Required to tell Jest this is an ES module
};
});
// OR more commonly — mock the entire module's default as a jest.fn()
jest.mock('./utils/logger', () => ({
__esModule: true,
default: jest.fn(),
}));
// Access in tests
import log from './utils/logger';
// log is now the jest.fn() from the mock
test('calls logger', () => {
doSomething();
expect(log).toHaveBeenCalledWith('expected message');
});Class mocks:
// Original: services/EmailService.js
export class EmailService {
async send(to, subject, body) {
// Real email sending logic
}
}
// Mock the class
jest.mock('./services/EmailService', () => {
return {
EmailService: jest.fn().mockImplementation(() => ({
send: jest.fn().mockResolvedValue({ messageId: 'test-123' }),
})),
};
});
import { EmailService } from './services/EmailService';
test('sends email', async () => {
const service = new EmailService();
await service.send('[email protected]', 'Hello', 'World');
expect(service.send).toHaveBeenCalledTimes(1);
});Fix 4: Fix TypeScript Type Errors on Mocked Functions
TypeScript doesn’t know a function is a Jest mock — use jest.mocked():
// Without type fix — TypeScript error
import { fetchUser } from './api';
jest.mock('./api');
test('...', () => {
fetchUser.mockResolvedValue({ name: 'Alice' }); // TS Error: Property 'mockResolvedValue' does not exist
});// CORRECT — use jest.mocked() to get typed mock
import { fetchUser } from './api';
jest.mock('./api');
test('...', async () => {
// jest.mocked() wraps the function with Jest's mock type information
jest.mocked(fetchUser).mockResolvedValue({ id: 1, name: 'Alice' });
const user = await getUser(1);
expect(user.name).toBe('Alice');
});Cast with as jest.Mock (older approach):
(fetchUser as jest.Mock).mockResolvedValue({ name: 'Alice' });
// Or extract for readability
const mockFetchUser = fetchUser as jest.Mock;
mockFetchUser.mockResolvedValue({ name: 'Alice' });With TypeScript strict mode, prefer jest.mocked() — it preserves the original function’s type signature while adding mock methods.
Fix 5: Clear and Reset Mocks Between Tests
Mocks accumulate state (call counts, return values) between tests unless explicitly cleared:
jest.mock('./api', () => ({
fetchUser: jest.fn(),
}));
const { fetchUser } = require('./api');
// BAD — mocks accumulate state between tests
test('test 1', () => {
fetchUser.mockReturnValue({ name: 'Alice' });
// ...
});
test('test 2', () => {
// fetchUser still returns { name: 'Alice' } from test 1
// and call count includes calls from test 1
expect(fetchUser).not.toHaveBeenCalled(); // FAILS — called in test 1
});// GOOD — clear mocks between tests
beforeEach(() => {
jest.clearAllMocks();
// Clears: call counts, instances, results, and mock.calls
// Does NOT reset implementation (mockReturnValue, mockImplementation)
});
// OR reset implementation too
beforeEach(() => {
jest.resetAllMocks();
// Clears everything + resets implementation to undefined
});
// OR restore original implementation (for jest.spyOn)
afterEach(() => {
jest.restoreAllMocks();
// Restores original implementation for spies
});Configure globally in jest.config.js:
// jest.config.js
module.exports = {
clearMocks: true, // Equivalent to jest.clearAllMocks() before each test
resetMocks: false, // Don't reset implementations automatically
restoreMocks: false, // Don't restore spies automatically
};Fix 6: Use jest.spyOn for Partial Mocking
When you only want to mock specific methods of a module (not the entire module):
import * as api from './api';
test('mocks a specific function', async () => {
// Replace just fetchUser — other exports remain real
const spy = jest.spyOn(api, 'fetchUser').mockResolvedValue({ name: 'Alice' });
const result = await api.fetchUser(1);
expect(result.name).toBe('Alice');
expect(spy).toHaveBeenCalledWith(1);
// Restore original after test
spy.mockRestore();
});
// OR use afterEach with restoreAllMocks
afterEach(() => {
jest.restoreAllMocks();
});Spy on class methods:
import { UserService } from './UserService';
test('mocks service method', async () => {
const service = new UserService();
jest.spyOn(service, 'findById').mockResolvedValue({
id: 1, name: 'Alice',
});
const user = await service.findById(1);
expect(user.name).toBe('Alice');
});Fix 7: Handle ES Module Mocking
With native ESM (without Babel transpilation), jest.mock() hoisting doesn’t work. Use jest.unstable_mockModule() for native ESM:
// With native ESM and "--experimental-vm-modules"
// jest.config.js
// { "transform": {} } // No Babel transform
// In test file
const { fetchUser } = await import('./api');
// WRONG — jest.mock() won't hoist in native ESM
jest.mock('./api'); // ← Doesn't work with native ESM
// CORRECT — use unstable_mockModule (Promise-based)
await jest.unstable_mockModule('./api', () => ({
fetchUser: jest.fn().mockResolvedValue({ name: 'Alice' }),
}));
// Dynamic import AFTER the mock is set up
const { fetchUser } = await import('./api');
test('mocks fetchUser', async () => {
const result = await fetchUser(1);
expect(result.name).toBe('Alice');
});Recommended: use Babel or ts-jest to avoid native ESM complexity — see Fix: Jest ESM Error for details on configuring Babel with Jest.
Still Not Working?
Check if the module has side effects on import — if the module runs code on import that calls the function you’re trying to mock, the mock isn’t set up yet:
// problematic-module.js
import { track } from './analytics';
track('module-loaded'); // ← Runs at import time, before jest.mock can intercept
// Solution: restructure to delay the call, or use jest.mock with a factory
// that prevents the side effectVerify mock is applied to the right path — the path in jest.mock() must exactly match the import path in the module under test (not the test file):
// Module under test (userService.js) imports:
import { query } from '../db/database'; // ← This path
// Mock must use the SAME path relative to the module under test
// From the test file's perspective:
jest.mock('../db/database'); // ← Must resolve to the same fileUse jest.mock() with the module path as seen from the test file — Jest resolves both to the same absolute path.
Manual mock with __mocks__ directory — create __mocks__/api.js next to api.js. Jest uses it automatically for manual mocks:
src/
├── api.js ← Original
├── __mocks__/
│ └── api.js ← Automatic mock (used when jest.mock('./api') is called)
└── userService.test.jsFor related Jest issues, see Fix: Jest Fake Timers Not Working and Fix: Jest ESM Error.
Solo developer based in Japan. Every solution is cross-referenced with official documentation and tested before publishing.
Was this article helpful?
Related Articles
Fix: Jest Setup File Not Working — setupFilesAfterFramework Not Running or Globals Not Applied
How to fix Jest setup file issues — setupFilesAfterFramework vs setupFiles, global mocks not applying, @testing-library/jest-dom matchers, module mocking in setup, and TypeScript setup files.
Fix: Jest Async Test Timeout — Exceeded 5000ms or Test Never Resolves
How to fix Jest async test timeouts — missing await, unresolved Promises, done callback misuse, global timeout configuration, fake timers, and async setup/teardown issues.
Fix: Jest Coverage Not Collected — Files Missing from Coverage Report
How to fix Jest coverage not collecting all files — collectCoverageFrom config, coverage thresholds, Istanbul ignore comments, ts-jest setup, and Babel transform issues.
Fix: Jest Fake Timers Not Working — setTimeout and setInterval Not Advancing
How to fix Jest fake timers not working — useFakeTimers setup, runAllTimers vs advanceTimersByTime, async timers, React testing with act(), and common timer test mistakes.