Resource library

QA How-To

Jest mocking modules: A QA Guide (2026)

Learn Jest mocking modules qa patterns for factories, partial and manual mocks, ESM, TypeScript, isolation, cleanup, and reliable dependency-focused tests.

23 min read | 2,903 words

TL;DR

`jest.mock()` replaces a module for the test file, usually through an automatic mock or factory. Mock stable boundaries, provide the correct export shape, configure behavior per test, and clean up calls and implementations deliberately. Native ESM requires registering `jest.unstable_mockModule()` before a dynamic import.

Key Takeaways

  • Mock an owned boundary such as a transport, clock, or provider, not every helper in the call graph.
  • Match the real module's export shape exactly, including default and named exports.
  • Use `jest.requireActual()` for narrow partial mocks and understand the coupling it introduces.
  • Register native ESM mocks before dynamically importing the subject.
  • Choose clear, reset, restore, or module-registry isolation based on the state you need to remove.
  • TypeScript types improve mock ergonomics but do not prove runtime mock shape.
  • Assert the subject's behavior first, then use interaction assertions only where the call is part of the contract.

Jest mocking modules qa tests replace a dependency at the module-loader boundary so the subject can be exercised without a real network, clock, file system, or third-party provider. The technique is powerful, but a mock with the wrong export shape or timing can make a test pass against a system that cannot exist in production.

The safest approach is to mock a small, owned boundary, configure only the behavior required by the scenario, assert the subject's public result, and reset state deliberately. This guide covers CommonJS and transformed-module patterns, partial and manual mocks, native ESM constraints, TypeScript typing, module caches, and a practical design rubric.

TL;DR

Need Jest tool Main caution
Replace a dependency jest.mock(name, factory?) Factory must match export shape
Configure a function jest.fn() and mock methods Reset behavior between cases
Keep most real exports jest.requireActual() Test now runs real module code
Reusable filesystem mock __mocks__ directory Activation and path rules matter
Observe real object method jest.spyOn() Restore original after test
Fresh module registry jest.resetModules() Can add cost and duplicate instances
Scoped fresh loading jest.isolateModules() Imports must occur inside callback
Mock native ESM jest.unstable_mockModule() Register before dynamic import
Type a TS mock jest.mocked() or jest.Mocked Runtime shape still matters

A module mock is not a free unit-test badge. If the assertion merely repeats values programmed into the mock, the test has little signal. Exercise transformation, branching, error handling, or orchestration owned by the subject.

1. Jest mocking modules qa: Decide What to Replace

A module mock intercepts imports or requires and supplies a replacement export object. Good candidates are slow, nondeterministic, destructive, expensive, or unavailable dependencies: HTTP transports, payment sandboxes, time providers, file adapters, feature-flag clients, and telemetry sinks. The mock gives the test deterministic control over success, failure, and edge responses.

Poor candidates are pure helpers whose real behavior is fast and central to the subject. Mocking every internal function turns a refactor into widespread test maintenance and proves only that configured mocks return configured values. If a price service calls a discount helper, mocking the helper may conceal the exact calculation the test should protect.

Choose the boundary by ownership and contract. A service can use a mocked userRepository because database access belongs outside its unit. An API route integration test may instead use the real repository against an isolated database and mock only an external email provider. Different layers need different doubles.

Name the double precisely. A mock replaces behavior and records interactions. A stub supplies controlled responses. A spy observes calls, often while preserving real behavior. Jest mock functions can act as several kinds, but using accurate language clarifies intent during review.

For a broader testing pyramid, see unit versus integration testing for QA. Module mocks should create fast feedback without eliminating the integration coverage that verifies real wiring.

2. Create a Runnable CommonJS Module Mock

CommonJS provides the least ambiguous first example because Jest can load it without an ESM transform. Install Jest and add a test script:

npm install --save-dev jest
npm pkg set scripts.test=jest

Create a dependency and subject:

// user-api.cjs
async function fetchUser() {
  throw new Error('Real API not configured in unit tests');
}

module.exports = {fetchUser};
// greeting-service.cjs
const {fetchUser} = require('./user-api.cjs');

async function greetingFor(userId) {
  const user = await fetchUser(userId);
  return user.active ? `Welcome, ${user.name}` : `Account inactive: ${user.name}`;
}

module.exports = {greetingFor};

The test factory returns the same named-export shape as the real dependency:

// greeting-service.test.cjs
jest.mock('./user-api.cjs', () => ({
  fetchUser: jest.fn(),
}));

const {fetchUser} = require('./user-api.cjs');
const {greetingFor} = require('./greeting-service.cjs');

afterEach(() => {
  jest.resetAllMocks();
});

test('greets an active user', async () => {
  fetchUser.mockResolvedValue({name: 'Ava', active: true});

  await expect(greetingFor('u-42')).resolves.toBe('Welcome, Ava');
  expect(fetchUser).toHaveBeenCalledWith('u-42');
});

This test proves branching and output owned by the subject. It also checks the dependency argument, which is part of the orchestration contract. Running npm test should execute it directly.

3. Match Named, Default, and Class Export Shapes

A factory must mimic the runtime export shape consumed by the subject. If production code destructures {fetchUser}, return an object with fetchUser. If it requires a function directly, return a mock function. A default export transformed through Jest may require a default property and, depending on the transform, __esModule: true. Do not add __esModule mechanically to CommonJS factories that do not need it.

Real consumption Factory result concept Frequent failure
const {send} = require('./mail') {send: jest.fn()} Returning one function
const create = require('./factory') jest.fn() Returning {create}
import {send} from './mail' {send: jest.fn()} Misspelled named key
import client from './client' through transform {__esModule: true, default: ...} Missing default wrapper
new Client() Mock constructor function or class Returning plain object

A class mock must be constructible if the subject uses new. An arrow function cannot be called as a constructor, so return jest.fn().mockImplementation(() => instance) or a suitable class shape. Then assert constructor arguments only if construction is a meaningful contract.

Export mismatches often surface as x is not a function, x is not a constructor, or cannot read property of undefined. Inspect the actual module and the subject's import syntax before adding casts. TypeScript casting can silence the compiler while leaving the runtime object wrong.

Prefer named exports for test-facing adapters when the project style allows it. They are explicit and easier to partially mock. The architectural goal remains a stable boundary, not a syntax optimized solely for Jest.

4. Configure Success, Failure, and Sequences

Mock functions provide per-test behavior with mockReturnValue, mockResolvedValue, mockRejectedValue, and one-time variants. Use resolved and rejected helpers for promise-returning dependencies so the double matches the real async contract. Throwing synchronously from an async dependency can exercise a different path.

test('returns a fallback after a provider failure', async () => {
  fetchUser.mockRejectedValue(new Error('provider unavailable'));

  await expect(safeGreetingFor('u-42')).resolves.toBe('Welcome');
});

A sequence can model retry behavior:

fetchUser
  .mockRejectedValueOnce(new Error('temporary'))
  .mockResolvedValueOnce({name: 'Ava', active: true});

Assert the final outcome, number of attempts, and arguments. Avoid a long chain of one-time responses that simulates an entire external service protocol inside one unit test. A fake server or integration fixture becomes clearer when response behavior is stateful and complex.

Keep error objects realistic enough for the subject's contract. If production branches on error.code, include that property rather than rejecting with an arbitrary string. Do not reproduce a third-party library's undocumented internal object in every test. Translate provider errors into an owned application error at the adapter, then test consumers against the owned contract.

When generated values help create payloads, Faker test data patterns shows how to keep random inputs reproducible. Mock responses should still make scenario-defining fields explicit.

5. Use Partial Mocks With requireActual

A partial mock preserves most real exports and replaces one boundary. jest.requireActual() bypasses the mock registry for that module, allowing the factory to spread real exports and override a selected function.

// audit.cjs
function formatEvent(type) {
  return type.trim().toUpperCase();
}

function currentTimestamp() {
  return new Date().toISOString();
}

module.exports = {formatEvent, currentTimestamp};
jest.mock('./audit.cjs', () => {
  const actual = jest.requireActual('./audit.cjs');
  return {
    ...actual,
    currentTimestamp: jest.fn(() => '2026-07-13T00:00:00.000Z'),
  };
});

const {formatEvent, currentTimestamp} = require('./audit.cjs');

test('keeps formatting real while controlling time', () => {
  expect(formatEvent(' created ')).toBe('CREATED');
  expect(currentTimestamp()).toBe('2026-07-13T00:00:00.000Z');
});

Partial mocks reduce duplication of export shapes, but loading the actual module can execute top-level side effects. If importing a module connects to a service or reads environment state, it is not a safe factory ingredient. Refactor side effects behind explicit functions.

Partial mocking also couples the test to module composition. If two functions in the same module reference each other through local bindings, replacing the exported property may not intercept the internal call. Mock an external dependency or inject it instead of expecting an export patch to rewrite lexical references.

Use a clock abstraction or Jest fake timers for widespread time behavior. The Jest fake timers guide covers clock and scheduling control more accurately than mocking every Date helper module.

6. Create Manual Mocks for Reused Boundaries

A manual mock lives in a __mocks__ directory and supplies a reusable implementation. For a user module, place the mock adjacent according to Jest's documented path rules, such as models/__mocks__/user.cjs for models/user.cjs. Node-package mocks use a project-level __mocks__ location adjacent to node_modules, subject to configured roots. Directory name casing matters on case-sensitive CI systems.

Manual mocks are useful for a filesystem abstraction, native bridge, or shared provider double used across many files. They should expose controls that tests can reset and understand. Avoid a giant manual mock that implements a second, subtly different version of the provider.

// __mocks__/notification-client.cjs
const send = jest.fn();
module.exports = {send};

A test still activates the manual mock with jest.mock('./notification-client.cjs') when automocking is not enabled. The jest.mock() call must be in the same scope as the import or require that should receive the mock. Built-in Node modules are not automatically mocked, so they require an explicit mock call.

Keep manual mock state observable through mock functions, and reset it in lifecycle hooks. If different tests need substantially different protocols, a lightweight in-memory fake with explicit methods can be more maintainable. Name files and helper APIs according to the owned domain so a test reader can see whether a real adapter, mock, or fake is active.

7. Mock Native ESM Modules in the Correct Order

Native ESM evaluates static imports before code in the test body, so the CommonJS and transformed-Jest hoisting pattern does not directly apply. Jest's current ESM workflow provides jest.unstable_mockModule(). The factory is required and may be asynchronous. Register it before dynamically importing the subject.

// exchange-rate.js
export async function loadRate() {
  return 1;
}
// price-service.js
import {loadRate} from './exchange-rate.js';

export async function convert(amount) {
  const rate = await loadRate();
  return Math.round(amount * rate * 100) / 100;
}
import {expect, jest, test} from '@jest/globals';

jest.unstable_mockModule('./exchange-rate.js', () => ({
  loadRate: jest.fn().mockResolvedValue(1.25),
}));

const {convert} = await import('./price-service.js');

test('converts with the provider rate', async () => {
  await expect(convert(20)).resolves.toBe(25);
});

Jest's ESM support and this explicitly unstable API deserve upgrade attention. Follow the current version's setup requirements, including transforms and Node VM module support, rather than copying an old command. Isolate native ESM mocking behind a small number of tests when possible.

If a suite is migrating formats, read ESM versus CommonJS in test projects. Mixing hoisted CommonJS assumptions with native ESM import timing is a common source of mocks that silently fail to apply.

8. Keep TypeScript Module Mocks Type-Safe

TypeScript helps autocomplete and catches some mock configuration errors, but Jest still replaces modules at runtime. After jest.mock('./user-api'), use jest.mocked(importedValue) to obtain a typed mocked view, or use jest.MockedFunction and jest.Mocked types where appropriate. The call does not magically create a mock, so activation must already have happened.

import {jest} from '@jest/globals';
import * as userApi from './user-api';

jest.mock('./user-api');
const mockedUserApi = jest.mocked(userApi);

mockedUserApi.fetchUser.mockResolvedValue({
  id: 'u-42',
  name: 'Ava',
  active: true,
});

If the mock data does not satisfy the return type, fix the fixture rather than casting through unknown. Broad casts allow missing fields and impossible states to reach tests, weakening the compiler's benefit. For large response types, use a typed factory with valid defaults and scenario overrides.

Deep mocked types can imply that every nested function is mocked, but runtime behavior depends on how the module mock was created. Automatic mocks transform supported properties, while a custom factory creates exactly what it returns. Match the type technique to that runtime reality.

Do not couple test code to the provider SDK's huge type when the application uses three fields. An owned adapter can map external types to a small domain interface. Tests then mock the interface boundary and remain stable through SDK upgrades.

9. Clear Calls, Reset Behavior, Restore Spies, and Isolate Modules

Cleanup APIs sound similar but remove different state. jest.clearAllMocks() clears call history and instances while preserving configured implementations. jest.resetAllMocks() also replaces mock implementations with fresh empty mocks. jest.restoreAllMocks() restores originals for spies and replaced properties where supported. It cannot restore an arbitrary factory mock to a real module.

Need after a test Use Effect
Remove call history only clearAllMocks Keeps behavior
Remove calls and configured mock behavior resetAllMocks Returns mocks to empty defaults
Put spied methods back restoreAllMocks Restores original property
Reload modules from fresh registry resetModules Clears module cache registry
Fresh registry inside one callback isolateModules Sandboxes loaded modules

Choose one deliberate lifecycle policy. Resetting all implementations in afterEach is safe when every test configures its own response. Clearing calls is appropriate when a stable default mock implementation is established in beforeEach. Restoring spies should be routine.

jest.resetModules() is for module-level state or environment-dependent initialization, not ordinary mock call cleanup. After resetting, require or import the subject again. jest.isolateModules() creates a sandbox registry for modules loaded inside its callback, which limits scope. Excessive registry resets slow the suite and can create multiple instances of singleton-like libraries. Prefer removing mutable module state from production design.

10. Jest mocking modules qa: Review Test Quality

A strong module-mock test reads as a business scenario. It configures a meaningful dependency result, invokes the public subject, asserts the returned or visible behavior, and optionally verifies an important interaction. It does not assert every internal call in order unless order is the actual contract.

Use this review checklist:

  1. Is the mocked module an appropriate boundary for this test layer?
  2. Does the factory exactly match the runtime export shape?
  3. Does async behavior resolve or reject the way the real API does?
  4. Does the assertion test logic beyond the value programmed into the mock?
  5. Are arguments or call counts contractual rather than incidental?
  6. Is state cleared, reset, restored, or isolated intentionally?
  7. Is there integration coverage proving the real modules wire together?
  8. Would a small interface or dependency parameter be clearer than loader interception?

Contract tests help keep mocks honest. Validate an adapter against the real sandbox or a representative local server, then let unit tests mock the owned adapter response. When a provider schema changes, update the adapter and its contract test rather than allowing dozens of hand-built provider mocks to drift.

A mock should make a hard-to-produce path easy to test, such as rate limiting, timeout, malformed response, or provider rejection. If it merely avoids writing a simple deterministic helper, it probably reduces confidence.

A mock contract review exercise

For each important mock, compare four artifacts: the owned interface, the real adapter implementation, the mock factory, and one integration or contract test. The method names, promise behavior, error categories, and required response fields should agree. This review is more valuable than duplicating an entire third-party schema, because consumers should depend on the smaller owned contract.

Include one assertion that would fail if the subject stopped making a meaningful decision. If the mock returns an active user and the subject simply returns the same object, the test may be testing no logic. A better case verifies authorization, mapping, retry classification, redaction, or another responsibility owned by the subject. Interaction assertions then support that outcome rather than replace it.

Review negative paths for realism. A timeout adapter might reject with an owned TimeoutError, while a missing record may resolve to null. Swapping those behaviors in a mock can send execution through a branch that production never uses. Contract tests and typed factories reduce this drift, but reviewers should still compare semantics.

Finally, keep loader mechanics out of most specifications. A small setup helper can expose a typed mocked adapter, but it should not hide which dependency was replaced or install broad global state. When many tests need complicated reset sequences, the production module may hold too much singleton state. Refactoring toward explicit construction can make both the code and the tests easier to reason about.

Interview Questions and Answers

Q: What does jest.mock() do?

It registers a replacement for a module in Jest's module system for the relevant test scope. With no factory, Jest can create an automatic mock. With a factory, the returned value must match the exports the subject consumes.

Q: What is the difference between a mock and a spy?

A mock replaces behavior and can record calls. A spy observes a method and, by default, may call the real implementation until configured otherwise. I restore spies after each test because they modify an existing object property.

Q: When would you use jest.requireActual()?

I use it for a narrow partial mock when most module behavior should remain real and importing the module has no unsafe side effects. I avoid it when internal lexical references mean an export override cannot intercept the call I care about.

Q: Why does an ESM mock need dynamic import?

Static ESM imports link before test-body code executes. Jest's ESM mock must be registered first, then the subject is dynamically imported so its dependency resolves through the mock registry.

Q: How do clear, reset, and restore differ?

Clear removes call history, reset also removes configured implementations, and restore puts spied or replaced properties back to their originals. Module factories and module registry state require different controls.

Q: How do you avoid over-mocking?

I mock external or architectural boundaries and run pure internal logic for real. Assertions focus on public behavior. Integration and contract tests verify that mocked boundaries match actual wiring and response shapes.

Q: How do you type Jest mocks in TypeScript?

After activating the runtime mock, I use jest.mocked() or Jest mock utility types for a typed view. I avoid unsafe casts and use typed factories for response objects. Types improve authoring but do not replace checking the runtime export shape.

Common Mistakes

  • Returning a function when the subject expects a named export object, or the reverse.
  • Mocking every pure helper and testing only implementation choreography.
  • Rejecting with an unrealistic value that does not match the real async error contract.
  • Registering a native ESM mock after statically importing the subject.
  • Assuming jest.mocked() creates the runtime mock by itself.
  • Using partial mocks when importing the real module triggers side effects.
  • Clearing call history when the next test also needs implementations reset.
  • Expecting restoreAllMocks() to undo arbitrary module factory mocks.
  • Calling resetModules() everywhere instead of removing mutable singleton state.
  • Omitting integration or contract coverage that checks mocks against real wiring.

Conclusion

Jest mocking modules qa tests are most credible when they replace one clear boundary and preserve the subject's real decisions. Match export shapes, model success and failure accurately, handle native ESM import timing, and choose cleanup based on the exact state created. Type helpers and partial mocks are useful, but neither can rescue an invalid runtime double.

Start with one slow or nondeterministic dependency. Wrap it in an owned adapter if necessary, write one contract test for the real integration, and mock that adapter in focused unit scenarios. The result is faster feedback without pretending that mocked wiring has already been proven.

Interview Questions and Answers

How does Jest module mocking work?

Jest's module system returns a registered automatic or factory replacement when the subject loads that specifier. The replacement must match the runtime export contract. Mock timing differs between CommonJS, transformed imports, and native ESM.

What should you mock in a unit test?

I mock slow, nondeterministic, destructive, or external boundaries such as transports and provider adapters. I run pure owned logic for real. The decision also depends on the test layer and available integration coverage.

How do you partially mock a module?

I call `jest.requireActual()` inside the mock factory, spread the real exports, and override the selected export. I first verify that import has no unsafe side effects and that the subject calls through an interceptable export boundary.

Why is native ESM mocking different?

Static ESM imports are linked before test code runs, so a later mock registration cannot change them. Jest's current pattern registers `unstable_mockModule` and then dynamically imports the subject. I isolate this unstable API and review upgrades.

Explain clear, reset, restore, and resetModules.

Clear removes mock call history. Reset also removes configured mock implementations. Restore returns spied or replaced object properties to originals, while resetModules clears the module registry so later loads create fresh module instances.

How do you type a mocked TypeScript module?

I activate the runtime mock, then use `jest.mocked()` or a matching Jest utility type. Mock return fixtures satisfy the real interface without broad casts. I still verify that the factory creates the correct runtime exports.

How do you keep mocks from drifting from production?

I mock an owned adapter contract, keep fixtures small and typed, and run contract or integration tests against the real boundary. Provider changes are translated in one adapter rather than copied across many unit mocks.

Frequently Asked Questions

How do I mock a module in Jest?

Call `jest.mock()` with the same module specifier used by the subject, optionally providing a factory. The factory result must match the consumed export shape. Load the subject after the mock registration according to the module system's timing rules.

What is a partial module mock in Jest?

A partial mock loads the real module through `jest.requireActual()` and overrides selected exports. It is useful when most behavior should remain real. Avoid it if importing the module has side effects or internal calls do not go through exported properties.

How do I mock an ESM module with Jest?

In Jest's native ESM workflow, register `jest.unstable_mockModule()` with a factory before dynamically importing the subject. Import Jest APIs from `@jest/globals`. Follow the installed Jest version's ESM setup because this area has explicit constraints.

What is the difference between clearAllMocks and resetAllMocks?

`clearAllMocks()` removes call history but keeps implementations. `resetAllMocks()` also resets mock behavior to fresh empty mocks. Use the one that matches how each test establishes defaults.

Does restoreAllMocks restore mocked modules?

It restores originals for spies and properties replaced through supported Jest APIs. It does not generally unregister an arbitrary `jest.mock()` factory and reload the real module. Module registry and mock registration need their own lifecycle design.

When should I use a manual mock directory?

Use a manual mock for a stable reusable double shared across tests, such as a provider or filesystem adapter. Keep it small, observable, and resettable. Activate it explicitly unless automocking and the module type make activation automatic.

Why is my Jest mock not a function?

The factory likely does not match the real named, default, direct-function, or class export shape. Compare the subject's exact import with the actual module exports. Do not use a TypeScript cast to conceal a runtime mismatch.

Related Guides