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
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 };
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():
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.
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
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:
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.