Skip to main content

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.fn or 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.js file with add(a, b) and subtract(a, b).
  • In tests, mock subtract to 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");
});
});