Jest Mock Hygiene: Choosing clearMocks, resetMocks, or restoreMocks
Decide which Jest mock cleanup mode to enable globally and when to use per-test cleanup instead.
10 Jul 2025, 05:54 UTC

The problem: tests that pass alone but fail together
A test that passes in isolation can fail when the full suite runs because a mock from an earlier test leaked into a later one. The leaked state is usually one of three things: call history, a mock implementation, or a module method replaced with jest.spyOn. This is called test pollution. Jest provides three global configuration keys to control cleanup: clearMocks, resetMocks, and restoreMocks.
Useful takeaway: enable at most one of these keys globally, understand exactly what each one touches, and add per-test cleanup in afterEach when a single global mode does not cover your mocks.
Decision and constraints
Decision: Which Jest mock cleanup strategy to use for a test suite.
Constraints:
- Tests must not share mock call history or implementations unless intentionally designed to do so.
jest.spyOnreplacements must not permanently replace original module methods.- Existing
jest.mockfactories andjest.fnimplementations should survive where the suite depends on them. - CI must run the full suite deterministically, not just individual test files.
Supported options compared
| Option | Clears before every test | Keeps | Best suited for | Main risk |
|---|---|---|---|---|
clearMocks | Mock call history and instances (mock.calls, mock.instances) | Mock implementations and jest.mock factories | Suites where mock behavior should persist but call counts must start at zero | Does not remove implementations, so stale return values can still leak |
resetMocks | Call history, instances, and mock implementations | jest.mock module factories | Suites that need fresh mock behavior in every test | Erases jest.fn implementations; tests may pass for the wrong reason if return values are not reapplied |
restoreMocks | Objects replaced by jest.spyOn | jest.fn mocks and jest.mock replacements | Suites that monkey-patch real methods with spies | Does not revert jest.fn or jest.mock; use another option for those |
These keys also exist as runtime APIs: jest.clearAllMocks, jest.resetAllMocks, and jest.restoreAllMocks. The runtime versions are useful for per-test cleanup in beforeEach or afterEach when you do not want a global setting.
Trade-offs to weigh
Global versus per-test cleanup
A global key runs before every test, which keeps configuration in one place. Per-test cleanup in afterEach gives finer control but must be added to every test file or a shared setup file. If your suite mixes jest.fn, jest.mock, and jest.spyOn, no single global key covers all of them, so per-test cleanup is often necessary.
At most one global key
Enable only one of clearMocks, resetMocks, or restoreMocks globally. Enabling more than one creates redundant or conflicting cleanup and makes it harder to reason about which mock state was removed and when.
What restoreMocks cannot do
restoreMocks only undoes replacements made with jest.spyOn. It does not restore originals for mocks created directly with jest.fn, and it does not undo jest.mock module replacements. If your suite relies heavily on jest.fn, restoreMocks alone is insufficient.
What resetMocks can break
resetMocks removes mock implementations before every test. If a test sets an implementation in beforeAll and expects it to persist across tests in the same file, that implementation will be gone by the second test. Reapply implementations in beforeEach or the individual test instead.
Concrete implementation
This example enables clearMocks globally. Place the configuration in your project's Jest config file.
// jest.config.js
module.exports = {
clearMocks: true,
};
The following test file shows how call history behaves with clearMocks enabled.
// user.service.test.js
import { getUser } from './user.service.js';
import api from './api.js';
jest.mock('./api.js');
describe('getUser', () => {
test('returns user data on success', async () => {
api.fetchUser.mockResolvedValue({ id: 1, name: 'Ada' });
const result = await getUser(1);
expect(result).toEqual({ id: 1, name: 'Ada' });
expect(api.fetchUser).toHaveBeenCalledTimes(1);
});
test('starts with zero call history', async () => {
api.fetchUser.mockResolvedValue({ id: 2, name: 'Grace' });
const result = await getUser(2);
expect(result).toEqual({ id: 2, name: 'Grace' });
expect(api.fetchUser).toHaveBeenCalledTimes(1);
});
});
Run the suite from the project root:
npx jest user.service.test.js
Required permissions: read and write access to the project's configuration files and test files; no elevated privileges are needed. Expected check: both tests pass and call counts remain isolated between tests. Risk: changing clearMocks to resetMocks globally can remove implementations set in beforeAll, causing tests that depend on those implementations to fail or pass for the wrong reason.
Validation steps
- Run
npx jest --versionand confirm the installed major version against the Jest changelog; defaults and deprecations can differ between versions. - Add a temporary test that asserts a mock's call count across two tests, toggle
clearMocksversusresetMocksin the config, and confirm whether history and implementation persist as expected. - Use
jest.spyOnto replace a method, enablerestoreMocks, then assert the original function reference is restored after the test completes. - Run the full suite, not just one file, to catch pollution that only appears when tests execute together.
Limitations
restoreMocksdoes not revert mocks made withjest.fnorjest.mock; pair it withclearMocksorresetMocksif your suite uses those.resetMockscan erasejest.mockimplementations if tests do not reapply them inbeforeEach.- Calling
jest.restoreAllMocksafter aspyOnreplacement can affect later tests if the shared object was mutated during the test. - Behavior is stable across recent Jest major versions, but confirm against your installed version before relying on these keys in CI.
Practical check: after changing the config, run the full suite locally and in CI. If tests that previously passed begin failing, inspect whether a mock implementation was removed by resetMocks or whether a spy was restored too early.
Rollback
Because this change modifies jest.config.js, rollback is straightforward: revert the configuration file to its previous state using version control.
git checkout -- jest.config.js
Then re-run the full suite to confirm the previous behavior returns.
0 replies
A thoughtful contribution can make all the difference. Be the first to share one.