Mastering Jest Manual Mocks: When to Replace Real Modules and How to Do It Right
Learn how Jest’s manual mocking lets you replace real modules with controlled stand‑ins, why it’s useful, how to set it up inline or via a __mocks__ folder, and what pitfalls to watch for. A concrete example and practical checklist help you apply this feature confidently.
24 May 2026, 21:36 UTC

Why Manual Mocks Matter
When a component pulls data from a configuration file, a database, or an external API, the test’s reliability hinges on that dependency behaving predictably. Manual mocking lets you replace the real module with a controlled stand‑in, ensuring your test only verifies the component’s logic, not the external system’s state.
Thesis: Manual Mocks Offer Precision, but Only if Used Wisely
Unlike automatic mocks, which Jest generates on the fly, manual mocks let you specify exactly what a dependency returns, how it records calls, and even swap implementations at runtime. This precision gives deterministic tests, but it also introduces maintenance overhead and the risk of hiding bugs in the real code.
How to Set Up a Manual Mock
1. Inline Mocking with jest.mock()
Place the jest.mock() call at the top of the test file, before importing the module under test. Jest will replace the requested module with the factory you provide.
// userService.test.js
jest.mock('./config', () => ({
getValue: () => 'mocked-value',
}));
import { getUser } from './userService';
import { getValue } from './config'; // this is the mocked function
test('uses mocked config value', () => {
const user = getUser();
expect(user.config).toBe('mocked-value');
expect(getValue).toHaveBeenCalled();
});
2. Global Mocks with a __mocks__ Folder
For modules that many tests need to mock, create a __mocks__ directory mirroring the module’s path. Jest automatically uses it when jest.mock() is called without a factory.
// __mocks__/config.js
export const getValue = jest.fn(() => 'mocked-value');
Then in any test:
jest.mock('./config'); // Jest pulls the file from __mocks__
Inspecting Mock Metadata
Mocked functions retain call history, arguments, and return values. This is invaluable for asserting that a dependency was invoked correctly.
expect(getValue).toHaveBeenCalledTimes(1);
expect(getValue).toHaveBeenCalledWith('user-id-42');
console.log(getValue.mock.calls); // [["user-id-42"]]
Resetting Mocks Between Tests
Jest clears mocks automatically after each test, but if you need a clean slate across an entire suite, use jest.resetModules() or jest.clearAllMocks() in a beforeEach hook.
beforeEach(() => {
jest.clearAllMocks();
jest.resetModules();
});
Trade‑Offs and Limitations
- Over‑mocking can hide bugs: If the real module contains logic that the test never exercises, you might miss regressions.
- API drift: When the real module’s interface changes, the manual mock must be updated, otherwise the test may pass with the mock but fail in production.
- Side‑effect differences: Some modules perform setup or cleanup on import. Mocking them can alter the global state your tests rely on.
- Module cache: Jest caches modules between test files. If a mock is defined in one test, it can leak into others unless you reset the module registry.
Practical Checklist Before Using Manual Mocks
- Confirm the dependency is truly external or non‑deterministic.
- Verify that the real module’s behavior is exercised elsewhere (e.g., integration tests).
- Keep the mock file in sync with the real API; run a linter or unit test that imports the real module to ensure compatibility.
- Document why the mock exists—future maintainers will appreciate the context.
Actionable Takeaway
Use manual mocks when you need deterministic, isolated tests, but keep the mock surface minimal and document its purpose. Pair them with integration tests that exercise the real module to avoid blind spots. By following the steps above, you’ll write tests that are both reliable and maintainable.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.