Skip to main content

Mocking in Jest

What is Mocking?

When unit testing, you want to test one thing in isolation. But real code depends on other things — databases, HTTP APIs, file systems, timers. Mocking replaces those dependencies with controlled fakes so your tests are:

  • Fast — no real network calls
  • Deterministic — same result every run
  • Isolated — failures point to the code under test, not dependencies

The boundary matters: mock what crosses out of your process, keep your own code real.

jest.fn() — Mock Functions

The simplest mock: a function that records how it was called.

const mockFn = jest.fn();

mockFn("hello", 42);
mockFn("world");

expect(mockFn).toHaveBeenCalledTimes(2);
expect(mockFn).toHaveBeenCalledWith("hello", 42);
expect(mockFn).toHaveBeenLastCalledWith("world");

Controlling Return Values

const mockFn = jest.fn();

mockFn.mockReturnValue(42); // always returns 42
mockFn.mockReturnValueOnce("first")
.mockReturnValueOnce("second"); // different each call

// For async functions
mockFn.mockResolvedValue({ id: 1, name: "Rizwan" });
mockFn.mockRejectedValue(new Error("DB error"));

Practical Example

src/emailService.js
function sendWelcomeEmail(user, mailer) {
if (!user.email) throw new Error("User has no email");
return mailer.send({
to: user.email,
subject: "Welcome!",
body: `Hi ${user.name}, welcome aboard!`,
});
}

module.exports = { sendWelcomeEmail };
src/emailService.test.js
const { sendWelcomeEmail } = require("./emailService");

test("sends welcome email with correct data", async () => {
const mockMailer = { send: jest.fn().mockResolvedValue({ success: true }) };
const user = { name: "Rizwan", email: "rizwan@example.com" };

await sendWelcomeEmail(user, mockMailer);

expect(mockMailer.send).toHaveBeenCalledWith({
to: "rizwan@example.com",
subject: "Welcome!",
body: "Hi Rizwan, welcome aboard!",
});
});

test("throws if user has no email", () => {
const mockMailer = { send: jest.fn() };
expect(() => sendWelcomeEmail({ name: "Rizwan" }, mockMailer)).toThrow("User has no email");
expect(mockMailer.send).not.toHaveBeenCalled();
});

jest.mock() — Mocking Entire Modules

Mock an entire module so every function in it is a jest.fn():

src/userController.test.js
const userService = require("./userService");
const { getUser } = require("./userController");

jest.mock("./userService"); // replaces all exports with jest.fn()

test("returns user from service", async () => {
userService.findById.mockResolvedValue({ id: 1, name: "Rizwan" });

const result = await getUser({ params: { id: 1 } });

expect(userService.findById).toHaveBeenCalledWith(1);
expect(result).toEqual({ id: 1, name: "Rizwan" });
});

Mocking Third-Party Libraries

jest.mock("axios");
const axios = require("axios");

test("fetches data from API", async () => {
axios.get.mockResolvedValue({
data: { posts: [{ id: 1, title: "Hello" }] },
});

const posts = await fetchPosts();

expect(axios.get).toHaveBeenCalledWith("https://api.example.com/posts");
expect(posts).toHaveLength(1);
});

Partial Mocks with jest.spyOn

Mock just one method of a module while keeping the rest real:

const fs = require("fs");
const { readConfig } = require("./config");

test("reads config file", () => {
const spy = jest.spyOn(fs, "readFileSync").mockReturnValue('{"port": 3000}');

const config = readConfig("./config.json");

expect(config.port).toBe(3000);
spy.mockRestore(); // restore original after test
});

Keeping Part of a Module Real with jest.requireActual

jest.mock replaces every export. When you only want to override one function from a dependency and keep its other exports working, spread the real module back in. Say ./pricing imports from a ./exchange-rate module that exports both fetchExchangeRate (network call) and formatCurrency (pure function):

jest.mock("./exchange-rate", () => ({
...jest.requireActual("./exchange-rate"),
fetchExchangeRate: jest.fn().mockResolvedValue(278.5),
}));

const { calculateTotal } = require("./pricing");

test("converts USD cart total to PKR using the current rate", async () => {
const total = await calculateTotal([{ price: 10 }, { price: 5 }], "PKR");
expect(total).toBe(4177.5);
});

calculateTotal runs its real logic and formatCurrency stays real; only the network-bound fetchExchangeRate is replaced. Without jest.requireActual, every other export of ./exchange-rate would be undefined.

This only works across module boundaries

If calculateTotal and fetchExchangeRate lived in the same module, this pattern would silently fail: calculateTotal calls its local fetchExchangeRate function directly, not the replaced export, so the real network call would still run. To make a function mockable, put it in its own module — or accept it as a parameter.

Mocking ES Modules (with Babel)

For ES module syntax:

// __mocks__/axios.js ← manual mock file
const axios = {
get: jest.fn(),
post: jest.fn(),
};
export default axios;

Or with jest.mock() in the test file — Jest hoists the call above imports automatically:

import axios from "axios";
import { fetchUser } from "./api";

jest.mock("axios");

test("fetches user", async () => {
axios.get.mockResolvedValue({ data: { name: "Rizwan" } });
const user = await fetchUser(1);
expect(user.name).toBe("Rizwan");
});

Manual Mocks with __mocks__/

For complex modules you always want to mock, create a __mocks__ folder next to the module:

src/
database.js
__mocks__/
database.js ← this is used automatically when jest.mock('./database') is called
src/__mocks__/database.js
const mockDb = {
connect: jest.fn().mockResolvedValue(true),
disconnect: jest.fn().mockResolvedValue(true),
query: jest.fn().mockResolvedValue([]),
create: jest.fn((data) => Promise.resolve({ id: Date.now(), ...data })),
};

module.exports = mockDb;

In tests, just call jest.mock('./database') and the manual mock is used.

Mocking Timers

Replace setTimeout, setInterval, and Date.now with controllable fakes. This is the quick version — Async Testing covers fake timers with async code, intervals, and retry logic in depth.

jest.useFakeTimers();

test("calls callback after delay", () => {
const callback = jest.fn();
setTimeout(callback, 1000);

expect(callback).not.toHaveBeenCalled();
jest.advanceTimersByTime(1000); // fast-forward 1 second
expect(callback).toHaveBeenCalledTimes(1);
});

afterEach(() => jest.useRealTimers()); // restore real timers

Mocking Date:

test("uses current date", () => {
jest.useFakeTimers(); // required — setSystemTime throws without fake timers enabled
jest.setSystemTime(new Date("2024-01-15"));

const result = getFormattedDate();
expect(result).toBe("2024-01-15");

jest.useRealTimers();
});

Clearing Mocks Between Tests

Mock state accumulates across tests unless you clear it. Three levels:

afterEach(() => {
jest.clearAllMocks(); // clears call history and return values
// jest.resetAllMocks(); // also removes mockReturnValue implementations
// jest.restoreAllMocks(); // also restores spies to original implementations
});

Or configure globally in jest.config.js:

jest.config.js
module.exports = {
clearMocks: true, // clears mock.calls and mock.instances between tests
resetMocks: false,
restoreMocks: false,
};

When NOT to Mock

Avoid mocking things you own. If you mock UserService to test UserController, you're testing that your mock works, not that the controller and service work together. Use mocks for:

  • Third-party services (email, payment, analytics)
  • External HTTP APIs
  • File system and environment
  • Timers and random values

For your own code's integration points, write integration tests that use the real implementations. The reasoning behind this rule gets a fuller treatment in Testing Best Practices.