Isolate Units with Jest Factory Mocks and Manual __mocks__
Replace network and I/O boundaries with deterministic fakes using Jest's factory mocks at the top of test files, and use manual __mocks__ for stable third-party dependencies. This guide covers hoisting behavior, configuration options, and common pitfalls that leak state across tests.
31 May 2026, 20:35 UTC

Use a top-level factory mock to make unit tests deterministic
Flaky tests from network calls, filesystem access or heavy third-party code are a sign you are testing a boundary, not a unit. The practical engineering decision is to replace that boundary with a deterministic fake using jest.mock with a factory at the top of the test file, and to keep test isolation with clearMocks and, when appropriate, a project-wide manual mock.
Factory mocks give you per-file control. Manual mocks in __mocks__ give you a single stable fake for a dependency used across many tests. ModuleNameMapper can redirect an import to a test double without touching source code. Together they keep tests fast and repeatable while avoiding over-mocking internals.
How Jest wires the mock before the module is evaluated
Jest hoists jest.mock calls to the top of the file before any imports or requires are evaluated. That means the mock is in place when the module under test is first loaded, even if the call appears later in the source.
A minimal configuration that supports this pattern:
// jest.config.js
module.exports = {
clearMocks: true,
resetModules: false
};
clearMocks clears call history between tests. resetModules false keeps the module cache per file, which is the default and avoids re-evaluating modules on every test.
Example test file for a module that calls an API boundary:
// api.js
exports.fetchUser = async (id) => {
const res = await fetch(`https://example.com/users/${id}`);
return res.json();
};
// userService.js
const api = require('./api');
exports.getUser = (id) => api.fetchUser(id);
// userService.test.js
jest.mock('./api', () => ({
fetchUser: jest.fn(() => Promise.resolve({ id: 1, name: 'A' }))
}));
const api = require('./api');
const service = require('./userService');
test('getUser returns mocked user', async () => {
const user = await service.getUser(1);
expect(api.fetchUser).toHaveBeenCalledTimes(1);
expect(user).toEqual({ id: 1, name: 'A' });
});
Run from the project root with read access to the repo:
jest --runInBand userService.test.js
Expected checks: api.fetchUser is a mock function, not the real implementation, and the resolved value matches the factory. The mock is created once per file, before require('./userService') runs, because of hoisting.
Manual mocks for stable third-party boundaries
Manual mocks in __mocks__ are auto-resolved when jest.mock('moduleName') is called without a factory. This is useful for stable third-party libraries where you want a single project-wide fake instead of repeating factories in each test.
ModuleNameMapper can redirect an import to a test double without touching source code. Combined with clearMocks it ensures each test starts with a clean mock state without manual reset.
Limits and common mistakes
Mocked modules are cached per test file unless resetModules is true, so state can leak across tests. Factory mocks run once per file, not per test, and hoisting means dynamic mock definitions based on runtime values are not possible.
jest.mock is hoisted; placing it inside a describe or after imports will still run before imports but makes intent unclear and can cause confusion about order of execution.
Over-mocking implementation details couples tests to internals and increases maintenance cost. Prefer mocking boundaries like network or I/O rather than internal helpers.
Manual mocks in __mocks__ apply to all tests unless explicitly unmocked with jest.unmock, which can hide real integration issues if used too broadly.
clearMocks only clears call history, it does not restore implementation. Use resetMocks or restoreAllMocks for implementation resets between tests.
Practical verification
Create a small module and a test file using jest.mock with a factory, run jest --runInBand and assert mock.calls and resolved values to confirm the mock replaces the real implementation. Add a __mocks__ file for a dependency, call jest.mock('moduleName') in a test, and verify the test receives the manual mock by checking exported values. Inspect jest.config.js options clearMocks, resetMocks and moduleNameMapper to confirm they affect test isolation as expected across multiple test files.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.