Mocking in Jest
Introduction
When testing, sometimes we don’t want to call the real dependency (like a database, API, or external service). Instead, we use a mock: a fake version that simulates the real thing.
Mocks let us:
- Control what data is returned.
- Avoid side effects (e.g., hitting an API).
- Test behavior in isolation.
Concepts
- Manual mocks: replace a module with your own fake implementation.
- Jest mocks: use
jest.fn()to create a fake function. - Spies: track how many times a function was called, with what arguments.
Examples
Basic Mock Function
const mockFn = jest.fn();
mockFn("Hello");
expect(mockFn).toHaveBeenCalled();
expect(mockFn).toHaveBeenCalledWith("Hello");
Mocking Property
const myMock1 = jest.fn();
const a = new myMock1();
console.log(myMock1.mock.instances);
// > [ <a> ]
const myMock2 = jest.fn();
const b = {};
const bound = myMock2.bind(b);
bound();
console.log(myMock2.mock.contexts);
// > [ <b> ]
// The function was called exactly once
expect(someMockFunction.mock.calls).toHaveLength(1);
// The first arg of the first call to the function was 'first arg'
expect(someMockFunction.mock.calls[0][0]).toBe('first arg');
// The second arg of the first call to the function was 'second arg'
expect(someMockFunction.mock.calls[0][1]).toBe('second arg');
Mocking Return Values
const myMock = jest.fn();
console.log(myMock());
// > undefined
myMock.mockReturnValueOnce(10).mockReturnValueOnce('x').mockReturnValue(true);
console.log(myMock(), myMock(), myMock(), myMock());
// > 10, 'x', true, true
const filterTestFn = jest.fn();
// Make the mock return `true` for the first call,
// and `false` for the second call
filterTestFn.mockReturnValueOnce(true).mockReturnValueOnce(false);
const result = [11, 12].filter(num => filterTestFn(num));
console.log(result);
// > [11]
console.log(filterTestFn.mock.calls[0][0]); // 11
console.log(filterTestFn.mock.calls[1][0]); // 12
Mocking Modules
// users.js
import axios from 'axios';
class Users {
static all() {
return axios.get('/users.json').then(resp => resp.data);
}
}
export default Users;
// users.test.js
import axios from 'axios';
import Users from './users';
jest.mock('axios');
test('should fetch users', () => {
const users = [{name: 'Bob'}];
const resp = {data: users};
axios.get.mockResolvedValue(resp);
// or you could use the following depending on your use case:
// axios.get.mockImplementation(() => Promise.resolve(resp))
return Users.all().then(data => expect(data).toEqual(users));
});
Mocking Implementations
- There are cases where it's useful to go beyond the ability to specify return values and full-on replace the implementation of a mock function.
- This can be done with
jest.fnor the mockImplementationOnce method on mock functions.
const myMockFn = jest.fn(cb => cb(null, true));
myMockFn((err, val) => console.log(val));
// > true
// foo.js
module.exports = function () {
// some implementation;
};
// test.js
jest.mock('../foo'); // this happens automatically with automocking
const foo = require('../foo');
// foo is a mock function
foo.mockImplementation(() => 42);
foo();
Mocking a Dependency
// userService.js
async function fetchUser(id) {
const response = await fetch(`/users/${id}`);
return response.json();
}
module.exports = { fetchUser };
// userService.test.js
const { fetchUser } = require('./userService');
global.fetch = jest.fn(() =>
Promise.resolve({
json: () => Promise.resolve({ id: 1, name: "Alice" })
})
);
test("fetchUser returns mocked user", async () => {
const user = await fetchUser(1);
expect(user.name).toBe("Alice");
});
Challenges
1. Mock logger
- Write a function
logger(message)and replace it with a mock in a test. - Check if it was called.
💡 Show Answer
// testCode.js
function logger(message) {
console.log(message);
}
module.exports = { logger };
// testCode.test.js
const { logger } = require("./testCode");
test("Logger correctly logs message", () => {
const mockLogger = jest.fn((msg) => console.log(msg));
mockLogger("Test message");
expect(mockLogger).toHaveBeenCalledWith("Test message");
});
2. Mock subtract
- Create a
math.jsfile withadd(a, b)andsubtract(a, b). - In tests, mock
subtractto always return 100.
💡 Show Answer
// testCode.js
function add(a, b) {
return a + b;
}
function subtract(a, b) {
return a - b;
}
module.exports = { add, subtract };
// testCode.test.js
const { add } = require("./testCode");
test("Add function returns correct result", () => {
expect(add(2, 3)).toBe(5);
expect(add(-1, 1)).toBe(0);
});
test("Subtract function returns correct result", () => {
const subtract = jest.fn((a, b) => 100);
expect(subtract(5, 3)).toBe(100);
expect(subtract(2, 4)).toBe(100);
});
3. Callback mock
- Use jest.fn() to simulate a callback function.
- Verify it was called with the right values.
💡 Show Answer
test("Callback function is called with correct arguments", () => {
const callback = jest.fn();
const a = 7, b = 8;
callback(a, b);
expect(callback).toHaveBeenCalledWith(a, b);
expect(callback).toHaveBeenCalledTimes(1);
});
4. Mock email
- Write a function sendEmail(to, msg) and mock it so that no real email is sent. Test that it was called with the correct arguments.
💡 Show Answer
// testCode.js
function sendEmail(to, msg) {
// Mock implementation: log instead of sending a real email
console.log(`Mock email sent to ${to}: ${msg}`);
}
module.exports = { sendEmail };
// testCode.test.js
const { sendEmail } = require("./testCode");
test("sendEmail is called with correct parameters", () => {
const mockSendEmail = jest.fn(sendEmail);
const to = "test@example.com";
const msg = "Hello, this is a test email.";
mockSendEmail(to, msg);
expect(mockSendEmail).toHaveBeenCalledWith(to, msg);
});
5. Fake API
- Create a fake API function that returns a promise.
- Test both success and failure cases using mocks.
💡 Show Answer
// testCode.js
function fakeApiCall(data) {
return new Promise((resolve) => {
setTimeout(() => {
resolve({ success: true, data });
}, 1000);
});
}
module.exports = { fakeApiCall };
// testCode.test.js
const { fakeApiCall } = require("./testCode");
test("fakeApiCall resolves with success and correct data", async () => {
const data = { email: "user@example.com", message: "Test message" };
const result = await fakeApiCall(data);
expect(result).toEqual({ success: true, data });
});
test("fakeApiCall can be mocked to simulate failure", async () => {
const mockFakeApiCall = jest
.fn()
.mockResolvedValue({ success: false, error: "Network error" });
const data = { email: "fail@example.com", message: "Should fail" };
const result = await mockFakeApiCall(data);
expect(result).toEqual({ success: false, error: "Network error" });
expect(mockFakeApiCall).toHaveBeenCalledWith(data);
});
6. spyOn greet
- Use jest.spyOn() to track calls to an object method.
💡 Show Answer
// testCode.js
class Person {
constructor(name, age) {
this.name = name;
this.age = age;
}
greet() {
return `Hello, my name is ${this.name}.`;
}
haveBirthday() {
this.age += 1;
return `Happy birthday! I am now ${this.age} years old.`;
}
}
module.exports = { Person };
// testCode.test.js
const { Person } = require("./testCode");
describe("Person class", () => {
let person;
beforeEach(() => {
person = new Person("John", 30);
});
test("greet method should be called", () => {
const greetSpy = jest.spyOn(person, "greet");
person.greet();
expect(greetSpy).toHaveBeenCalled();
greetSpy.mockRestore();
});
});
7. Mock Date.now
- Mock Date.now() so it always returns the same timestamp.
💡 Show Answer
describe("Date.now function Mocking", () => {
beforeAll(() => {
jest.spyOn(Date, "now").mockReturnValue(1625097600000);
});
test("Date.now retuns same mocked value when called", () => {
expect(Date.now()).toBe(1625097600000);
expect(Date.now()).toBe(1625097600000);
expect(Date.now()).toBe(1625097600000);
});
afterAll(() => {
Date.now.mockRestore();
});
});
8. Mock fs.readFileSync
- Mock a module that reads a file.
- Test your function without touching the real filesystem.
💡 Show Answer
// testCode.js
const fs = require("fs");
// Function to test: reads a file and returns its contents
function readFileContent(filePath) {
return fs.readFileSync(filePath, "utf8");
}
// testCode.test.js
jest.mock("fs");
describe("readFileContent", () => {
test("returns mocked file content", () => {
fs.readFileSync.mockReturnValue("mocked file data");
const result = readFileContent("fake/path.txt");
expect(result).toBe("mocked file data");
expect(fs.readFileSync).toHaveBeenCalledWith("fake/path.txt", "utf8");
});
});
9. Mock fetchUser
- Create a UserService with fetchUser.
- Mock the API so it always returns
{id: 1, name: "Bob"}.
💡 Show Answer
// testCode.js
class UserService {
fetchUser(name, age) {
return new Person(name, age);
}
}
module.exports = { UserService };
// testCode.test.js
const { UserService } = require("./testCode");
jest.mock("./testCode", () => {
return {
UserService: jest.fn().mockImplementation(() => ({
fetchUser: jest.fn(() => ({ id: 1, name: "Bob" })),
})),
};
});
test("fetchUser returns mocked API response", () => {
const userService = new UserService();
const user = userService.fetchUser("Alice", 30);
expect(user).toEqual({ id: 1, name: "Bob" });
});
10. success/fail mock
- Combine mocking with coverage: verify that your test hits both success and error branches.
💡 Show Answer
// testCode.test.js
const { UserService } = require("./testCode");
describe("UserService.fetchUser", () => {
let userService;
beforeEach(() => {
jest.resetModules();
jest.clearAllMocks();
userService = new UserService();
});
test("fetchUser returns mocked API response (success branch)", () => {
userService.fetchUser = jest.fn(() => ({ id: 1, name: "Bob" }));
const user = userService.fetchUser("Alice", 30);
expect(user).toEqual({ id: 1, name: "Bob" });
});
test("fetchUser throws error (error branch)", () => {
userService.fetchUser = jest.fn(() => { throw new Error("User not found"); });
expect(() => userService.fetchUser("Alice", 30)).toThrow("User not found");
});
});