What this usually means
The core issue is that Jest's module mocking relies on a hoisting mechanism that moves jest.mock() calls to the top of the file before any imports are executed. If the mock call is not hoisted (e.g., inside a describe or it block, or after an import), the import runs first and the real module is cached. Additionally, path mismatches (relative vs. absolute, missing extensions) or moduleNameMapper interference can cause the mock to not apply. Another common cause is that the module is already loaded before the mock is registered, especially when using factory functions incorrectly.
The first ten minutes — establish facts before touching code.
- 1Run the test with --verbose to see which mock implementations are actually used.
- 2Add console.log inside the mocked module to confirm if it's being called.
- 3Check that jest.mock() is at the top of the file, before any import statements.
- 4Temporarily rename the module to a unique string to see if the mock is resolved at all.
- 5Clear Jest's module cache with jest.resetModules() before the test.
The specific files, logs, configs, and dashboards that usually own this bug.
- searchThe test file where jest.mock() is called — verify its position relative to imports.
- searchThe __mocks__ directory relative to the module being mocked.
- searchJest configuration file (jest.config.js/ts) for moduleNameMapper settings.
- searchThe module's package.json or index.js for resolution quirks.
- searchThe test runner output for any warnings about mock hoisting.
- searchThe module cache — run with --clearMocks or --resetModules flag.
Practical causes, not theory. These are the things you will actually find.
- warningjest.mock() called inside describe or it block instead of at module scope.
- warningjest.mock() called after import statements (hoisting not working as expected).
- warningIncorrect path to the module (relative vs. absolute, missing ./ or ../).
- warningModule is a core module or already imported elsewhere before mock.
- warningFactory function passed to jest.mock() returns undefined or invalid mock.
- warningmoduleNameMapper overrides the mock path to a different file.
- warningUsing ES module syntax with jest.mock() where implementation is not hoisted.
Concrete fix directions. Pick the one that matches your root cause.
- buildMove jest.mock() to the very top of the test file, before any import statements.
- buildUse jest.mock('modulePath', () => mockFactory) with a factory that returns the mock object.
- buildFor ES modules, use jest.unstable_mockModule() or configure transform to handle hoisting.
- buildAdd jest.resetModules() before each test to clear module cache.
- buildUse __mocks__ manual mocks for complex modules that are hard to mock inline.
- buildCheck moduleNameMapper in Jest config and remove or adjust conflicting mappings.
A fix you cannot prove is a guess. Close the loop.
- verifiedAdd a test that calls the mock and expects it to have been called; if it passes, the mock is active.
- verifiedInsert a debugger or console.log in the mock factory to see when it runs.
- verifiedRun the test with --no-cache to rule out stale cache issues.
- verifiedTemporarily replace the module with a trivial one to confirm path resolution.
- verifiedUse jest.spyOn on the real module after mocking to see if it's replaced.
Things that make this bug worse or harder to find.
- warningPutting jest.mock() inside beforeEach or beforeAll — it won't work.
- warningUsing jest.mock with a variable or expression that relies on imports.
- warningForgetting to return the mock object from the factory function.
- warningAssuming jest.mock() works the same for ES modules (it doesn't fully).
- warningMocking a module that is also imported in setupFiles or other test files.
The Silent Fake: When jest.mock() Didn't Mock
Timeline
- 09:15CI fails on a PR that adds a new API endpoint; all tests that mock the payment service fail.
- 09:20I pull the branch, run the test suite locally; tests pass. CI uses --ci flag.
- 09:30I notice the failing tests use jest.mock() inside a describe block for context-specific mocks.
- 09:45I move the jest.mock() call to the top of the file, outside describe. Tests still fail in CI.
- 10:00I add a console.log in the mocked module's real file; CI logs show it's executing the real implementation.
- 10:15I check the Jest configuration; there's a moduleNameMapper rule that maps the payment service to a different path.
- 10:30I add a new test that checks that the mock is actually applied; it fails.
- 10:45I remove the conflicting moduleNameMapper rule and update the mock path to match the resolved path.
- 11:00Tests pass both locally and in CI after clearing the cache.
The CI was failing on a PR that added a new endpoint. The payment service mock wasn't working, so tests hit the real service and timed out. Local tests passed because of different module caching behavior. I started by moving the jest.mock() call to the top of the file, which is the textbook fix, but it didn't help.
I then added a console.log inside the real payment service module. When CI ran, the log appeared, confirming the mock wasn't being applied. I checked the Jest config and found a moduleNameMapper rule that was redirecting the module path to a different file. The mock was being applied to the original path, but the import resolved to the mapped path. So the mock was effectively on a module that was never imported.
I removed the conflicting moduleNameMapper rule and updated the jest.mock() path to match the resolved import path. After clearing Jest's cache (jest --clearCache), both local and CI tests passed. The lesson: always verify the actual resolved module path, and beware of moduleNameMapper overrides that can silently break mocks.
Root cause
A moduleNameMapper rule in Jest config aliased the module path, so jest.mock() targeted the original path but imports resolved to the aliased path, leaving the mock ineffective.
The fix
Removed the conflicting moduleNameMapper rule and aligned the jest.mock() path with the actual import resolution. Also cleared Jest cache to ensure fresh module resolution.
The lesson
Always verify the resolved module path when mocks fail. Use moduleNameMapper sparingly and document its effects on mocking.
Jest uses the Babel plugin babel-jest to transform test files. When it sees jest.mock(), it hoists that call to the top of the file, before any import statements. This ensures the mock is registered before the module is imported.
Hoisting only works if jest.mock() is a direct call at the module level. If it's inside a describe, it, or any function, hoisting does not happen because the call is not statically analyzable. The import runs first, and the real module is cached. Always put jest.mock() at the top of your test file, outside any blocks.
For ES modules, standard jest.mock() does not hoist because ES imports are static. Use jest.unstable_mockModule() instead, which is designed for ES modules and must be called before the dynamic import. Alternatively, configure Jest to use the CommonJS transform.
Jest resolves module paths based on the test file's location. If you use a relative path like '../utils/helper', ensure it matches exactly what the import statement uses. A common mistake is to include or omit './' inconsistently.
moduleNameMapper in Jest config can redirect module paths. If you have a mapping like '^@app/(.*)$': '<rootDir>/src/$1', and you mock '@app/service', the mock applies to that pattern. But if your import uses a relative path, the mapper might not apply. Check the resolved path by adding a temporary test that logs the module's filename.
Also watch for package.json 'exports' field or index.js resolution. If the module resolution is complex, the mock might target one entry point while the import uses another. Use jest.mock('module-name') with the exact string used in import.
jest.mock('module', () => factory) requires the factory to return an object with the module's exports. If the factory returns undefined, the mock will be empty. Always return an object that matches the module's API.
The factory function is evaluated before the module is imported, so you cannot use variables from the module inside the factory. If you need to share state, use jest.requireActual to get the real module and then override specific exports.
For default exports, the factory must return an object with a default property: () => ({ default: mockDefault }). ES module default exports are particularly tricky: use jest.unstable_mockModule with a factory that returns { default: ... }.
Jest caches modules after the first import. If a module is imported in a setup file or another test before your mock, the real module is cached and your mock won't apply. Use jest.resetModules() in a beforeEach to clear the module registry for each test.
However, jest.resetModules() also resets mocks created with jest.mock() that were hoisted. So you must re-mock after calling resetModules. A common pattern is to call jest.resetModules() and then jest.mock() in the test body (not hoisted), but that requires the mock to be inside the test.
Alternatively, use jest.isolateModules(fn) to run a test in an isolated module scope, which avoids cache contamination. This is useful if you need different mocks for different tests.
Run Jest with --verbose to see which mock implementations are used for each test. If you see 'Automock: false' or 'Mock: [function]', you can verify the mock is applied.
Use --showConfig to dump the resolved Jest configuration, including moduleNameMapper rules. This helps identify conflicting mappings.
Add the environment variable DEBUG=jest:mock* to see verbose logging about mock registration. This prints which mocks are registered and when.
Temporarily replace the module with a simple object to test path resolution: jest.mock('path/to/module', () => ({ __esModule: true, default: 'test' })).
Frequently asked questions
Why does jest.mock() work locally but not in CI?
Often due to module caching differences. CI may run tests in a different order, or a setup file imports the module before the test. Use jest.resetModules() to ensure a clean state. Also check for environment-specific moduleNameMapper rules that only exist in CI.
Can I use jest.mock() with ES modules?
Yes, but with caveats. Standard jest.mock() does not hoist ES imports. Use jest.unstable_mockModule() instead, which must be called before any dynamic import. Alternatively, configure Jest to transform ES modules to CommonJS via babel-jest with @babel/plugin-transform-modules-commonjs.
Why is my mock function undefined when I call it?
This usually means the mock factory returned an object that doesn't have that function. Ensure the factory returns an object with the correct shape. For default exports, the factory must return { default: mockFunction }. Also check if the module has multiple exports that need to be mocked.
How do I mock only one function from a module?
Use jest.mock() with a factory that imports the real module and replaces specific exports. Example: jest.mock('module', () => ({ ...jest.requireActual('module'), myFunc: jest.fn() })). This keeps other exports real.
What's the difference between jest.mock() and jest.spyOn()?
jest.mock() replaces the entire module with a mock, affecting all imports in the test file. jest.spyOn() wraps a specific method on an already imported object, preserving other methods. Use jest.mock() when you need to prevent side effects from the module's initialization; use jest.spyOn() for fine-grained method overrides.