Mocking modules that use import/export syntax works slightly differently than CommonJS require/module.exports. This lesson covers the specific patterns needed for ES modules.
Mocking Named and Default Exports
When a module uses ES module syntax, Jest (via Babel's CommonJS interop) still lets jest.mock() work the same way, but the shape of the mock factory needs to account for default exports specifically, since import Something from './module' maps to a default property under the hood.
Named exports (import { getUser } from './api') map directly to keys on the mocked module object, just like CommonJS.
// api.js
export function getUser(id) { /* real fetch logic */ }
export default function getConfig() { /* real fetch logic */ }
// api.test.js
jest.mock('./api', () => ({
getUser: jest.fn().mockResolvedValue({ id: 1, name: 'Ada' }),
default: jest.fn().mockReturnValue({ env: 'test' }),
}));
import getConfig, { getUser } from './api';
The factory's default key corresponds to the module's default export; other keys map to named exports.
Mocking a Single Named Export with spyOn
import * as api from './api';
jest.spyOn(api, 'getUser').mockResolvedValue({ id: 1, name: 'Ada' });
Importing * as api gives you an object you can jest.spyOn() for a single named export.
This avoids mocking the entire module when only one export needs to change.
This pattern requires Babel's ESM-to-CommonJS interop (the default in most Jest + Babel setups).
Native ESM support in Jest (via --experimental-vm-modules) has some restrictions around mutating imports this way.
Run Jest with NODE_OPTIONS=--experimental-vm-modules
Partially Mocking an ES Module
Just like CommonJS partial mocks, you can spread jest.requireActual() inside the factory to keep most named exports real while overriding just one or two.
Jest has experimental native ESM support that avoids Babel entirely, but it has stricter rules: modules are read-only, so patterns like jest.spyOn(api, 'getUser') on a live binding may not work the same way. Most projects still rely on Babel's CommonJS interop for the smoothest mocking experience.
Babel-based ESM (via @babel/preset-env) is the most common and best-supported setup for mocking.
Native ESM (--experimental-vm-modules) trades some mocking flexibility for closer spec compliance.
Check your project's Babel/transform configuration before assuming a mocking pattern will work.
Common Mistakes
Forgetting the default key when mocking a module with a default export.
Trying to reassign an imported binding directly instead of using jest.mock() or jest.spyOn().
Assuming native ESM mode supports every mocking trick that Babel-transformed CommonJS supports.
Mocking an entire module when a single jest.spyOn() on one named export would be simpler.
Key Takeaways
ES module mocking works through the same jest.mock() API, with a default key for default exports.
import * as module plus jest.spyOn() lets you mock a single named export.
Partial mocking with jest.requireActual() works the same as it does for CommonJS.
Babel-based ESM interop is generally the most flexible setup for mocking today.
Pro Tip
If a default export mock isn't taking effect, double-check your factory includes a default key — this is the single most common mistake when mocking ES modules in Jest.
You now know how to mock ES modules. Next, move into async testing and how Jest handles asynchronous code.