Jest manual mocks with __mocks__ directories
Replace dependencies in Jest tests using manual mocks stored in __mocks__ folders, with a worked example and limits.
08 Oct 2025, 06:41 UTC

Problem and takeaway
You need to replace a dependency in a Jest test with a fake, but writing an inline mock factory in every test file is repetitive and can drift from the real module shape. Jest manual mocks let you store the fake in a __mocks__ folder and activate it with jest.mock('module-name').
Takeaway: place __mocks__/module-name.js next to the real module, or next to node_modules for Node built-ins, then call jest.mock('module-name') with no factory. Jest loads your fake instead of the real module for that test file.
How manual mock resolution works
A manual mock is a JavaScript file that exports the same API as the real module. When jest.mock('module-name') runs without a second argument, Jest looks for __mocks__/module-name.js adjacent to the real module. If found, Jest substitutes the fake module into the test file’s module registry.
- User module:
src/notifications/email.jspairs withsrc/notifications/__mocks__/email.js. - Node built-in such as
fs:__mocks__/fs.jsmust sit next tonode_modules, not inside your source tree.
Mocks are scoped to the test file by default. A different test file that imports the same real module without calling jest.mock receives the real implementation.
Worked example
Assumptions: CommonJS modules and a recent Jest release. Check your installed version because ESM manual-mock behavior varies between major releases. No special Jest config is normally required beyond the transform for your file type.
Project layout
project/
package.json
node_modules/
src/
notifications/
email.js
__mocks__/
email.js
notifications.test.js
Real module
const nodemailer = require('nodemailer');
function send(to, subject) {
return nodemailer.createTransport().sendMail({ to, subject });
}
module.exports = { send };
Manual mock
const send = jest.fn();
module.exports = { send };
Test file
jest.mock('./notifications/email');
const { send } = require('./notifications/email');
test('email module is replaced by manual mock', () => {
expect(jest.isMockFunction(send)).toBe(true);
send('[contact removed]', 'Welcome');
expect(send).toHaveBeenCalledTimes(1);
expect(send).toHaveBeenCalledWith('[contact removed]', 'Welcome');
});
jest.mock is hoisted above imports by the Jest transform, so the mock is active when require runs. If you require the module before jest.mock, you receive the real module instead.
Configuration and verification
Run this command in the project root:
npx jest --showConfig
Confirm roots includes your source directory and moduleDirectories includes node_modules. No __mocks__ entry is normally required because Jest resolves adjacent mock folders by convention.
Practical check: assert that the imported function is a mock function, as shown above, and assert that no real side effect occurred—for example, no network request for email and no disk read for fs. If you rename the mock folder to __mocks_backup__, the same test should fail or receive the real module, proving that the folder name and location matter.
Limits and common mistakes
Mock state leaks between tests
Module-level jest.fn() objects persist for the duration of the test file. If one test changes the mock implementation, later tests see the change.
- Fix: call
jest.clearAllMocks()inbeforeEachto clear call history, orjest.resetModules()to force Jest to reload modules.
Importing before mocking
Because jest.mock calls are hoisted, placing them at the top of the file is safest. Importing the real module first bypasses the manual mock.
Wrong mock location for built-ins
jest.mock('fs') does not use src/__mocks__/fs.js. It uses __mocks__/fs.js next to node_modules.
Export shape mismatch
If the real module uses module.exports = function() {} but the mock exports { send }, imports resolve to undefined without a clear error. Match the real module’s export shape exactly.
Non-existent modules
For modules that do not exist on disk, use jest.mock('module-name', factory, { virtual: true }). A manual mock file alone is not enough.
ESM and TypeScript
Manual mock resolution for ECMAScript modules depends on the Jest version and VM modules configuration. TypeScript projects may need type assertions or ambient declarations so the mock shape satisfies the compiler.
Comparison: inline factory versus manual mock
| Approach | When to use | Drawback |
|---|---|---|
Inline factory in jest.mock | One-off behavior in one test file | Repeated across files; drifts from real module shape |
Manual mock in __mocks__ | Same fake reused across many tests | Static file; no per-test parameters |
jest.doMock | Late binding within one file | More complex setup than hoisted jest.mock |
Limitations
- Manual mocks are static files and cannot accept arguments. Use inline factories when different tests need different behavior for the same module.
- One manual mock file per module name per test-file context. Switching between two mocks for the same module requires
jest.doMockorjest.resetModules. - Adding mock files does not mutate existing code, so destructive rollback is not required; delete the
__mocks__file and removejest.mockcalls to revert.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.