A guide to mocking functions and modules with Jest

Updated 26 October 2024
·
jest
react

When writing Jest unit tests, I always struggle to remember the syntax for mocking ES6 modules. So this post is intended as a part-guide, part-cheatsheet to refresh your memory. It covers:

  • How to mock imported functions from a module using jest.mock() and jest.fn()
  • When to use other Jest functions like clearAllMocks, resetAllMocks and spyOn

Common scenarios where I might use module mocking include when I’ve imported external dependencies and want them mocked, mocking an endpoint call which won’t work in my local unit test, or I want to mock a specific error case so that I can test it is gracefully handled.

For the purposes of this page, let’s assume that we have a ES6 module that has two named exports (getTime and isMorning), and one default import:

time.js
// Our named exports
export const getTime = () => '14:30';
export const isMorning = () => false;
 
// Our default export
const getDayOfWeek = () => 'Monday';
export default getDayOfWeek;

Using Jest’s mock() function

We can mock the functions that a module returns using Jest’s .mock().

When defining your mocks, make sure to place them near the top of your test file, above and outside any of your test cases. Usually I put them just below my imports.

This is very important, as Jest behind-the-scenes hoists these .mock() calls to the top of your file anyway. So your mocks may not work if you don’t do this.

Mocking a named import function

If you wanted to mock a named import, say getTime():

// app.js
import { getTime } from './time';
 
// test.js
jest.mock('./time', () => ({
    getTime: () => '11:11',
}));

Mocking a default import

If instead of importing a named import, you wanted to import a default import, you would mock it like this:

// app.js
import getDayOfWeek from './time';
 
// test.js
jest.mock('./time', () => () => 'Monday');

Mocking both default and named imports together

If you want to mock default and named imports together, you’ll need to include __esModule: true:

// app/js
import getDayOfWeek, { getTime } from './time';
 
// test.js
jest.mock('./time', () => ({
    __esModule: true,
    default: () => 'Thursday'
    getTime: () => '11:11',
}));

Mocking only one function (and leaving other imports unmocked)

The above examples assume you want to mock the entire module. If the module exports multiple functions, and you wanted to leave most of them unmocked, you can use requireActual:

// test.js
jest.mock('./time', () => ({
    ...jest.requireActual('./time'),
    getTime: () => '11:11',
}));

This imports the real functions, so that geDayOfWeek and isMorning will be left unmocked.

Mocking for individual tests using jest.fn() and mockReturnValue()

With the above .mock() examples, you would be mocking the module and their functions once for the entire test file. If you wanted to be more specific, and change what the mock returns between unit tests, you can use jest.fn() and mockReturnValue().

For example, if you wanted to have getTime to return a different value per test, you would do the following:

import { getTime } from './time';
 
jest.mock('./time', () => ({
    getTime: jest.fn(),
}));
 
test('App renders 11:11', () => {
    getTime.mockReturnValue('11:11');
    // The rest of your test goes here!
});
 
test('App renders 12:12', () => {
    getTime.mockReturnValue('12:12');
    // The rest of your test goes here!
});

The key points to remember are that you must:

  1. Import the function you want to mock (on line 1).
  2. Mock the module with .mock() at the top of your test file, and then mock your desired function with jest.fn()
  3. Finally, inside of individual test, you can change what the mocked function returns for that test with mockReturnValue.

Note that using mockReturnValue inside of a test case will continue to apply for the tests that come after it:

import { getTime } from './time';
 
jest.mock('./time', () => ({
    getTime: jest.fn().mockReturnValue('11:11'),
}));
 
test('App renders 11:11', () => {
    // Test passes
});
 
test('App renders 12:12', () => {
    getTime.mockReturnValue('12:12');
    // Test passes
});
 
test('App renders 11:11, again', () => {
    // This test would fail, because the mock is now returning "12:12"
});

You have couple options to get around this:

  1. Mocking only one call with mockReturnValueOnce()
  2. Resetting your mocks before each test with beforeEach()

Using mockReturnValueOnce()

If you use mockReturnValueOnce(), it will mock the return value for the next function call, and then resume returning whatever was mocked previously.

import { getTime } from './time';
 
jest.mock('./time', () => ({
    getTime: jest.fn().mockReturnValue('11:11'),
}));
 
test('App renders 12:12', () => {
    getTime.mockReturnValueOnce("12:12");
    // Test passes
});
 
test('App renders 11:11', () => {
    // Test passes
});

Redefining the mocks in beforeEach()

Alternatively, you can define the mock before each test, and then call mockReturnValue inside individual tests when you want the mock to return something different. The following test will have the mock reset by the beforeEach call:

import { getTime } from './time';
 
jest.mock('./time', () => ({
    getTime: jest.fn().mockReturnValue('11:11'),
}));
 
beforeEach(() => {
   getTime.mockReturnValue("11:11");
});
 
test('App renders 11:11', () => {
    // Test passes
});
 
test('App renders 12:12', () => {
    getTime.mockReturnValue("12:12");
    // Test passes
});
 
test('App renders 11:11, again', () => {
    // Test passes
});

Personally I’d prefer this approach over using mockReturnValueOnce, as if you add more tests in the future you don’t have to worry about breaking anything. The beforeEach() will reset everything into a “clean” state for you.

Clearing mocks between tests with clearAllMocks()

As you write your tests, you might want to assert how many times a specific function was called.

After declaring a mock, its call count doesn’t reset between tests. So the second test here would fail:

jest.mock('./time', () => ({
    getTime: jest.fn().mockReturnValue('11:11'),
}));
 
test('Calls getTime function once', () => {
    render(<App />);
    expect(getTime).toBeCalledTimes(1);
});
 
test('Calls getTime function once, again', () => {
    render(<App />);
    expect(getTime).toBeCalledTimes(1);
    // This test would fail as getTime has been called twice
});

We can clear the call count between each test by calling clearAllMocks:

beforeEach(() => {
    jest.clearAllMocks();
});

Note that this just clears the number of times a mock has been called, and it doesn’t clear what the function has been mocked to return. For that, we need resetAllMocks.

Resetting mocks between tests with resetAllMocks()

Using Jest’s resetAllMocks function takes things one step further, and resets all things that have been mocked with jest.fn(). Once it’s been reset, you will need to explicitly mock the function again for it to work. If we take the following example:

import { getTime, isMorning } from './time';
 
jest.mock('./time', () => ({
    getTime: jest.fn().mockReturnValue('11:11'),
    isMorning: () => false,
}));
 
beforeEach(() => {
    jest.resetAllMocks();
});
 
test('App renders 11:11', () => {
    expect(getTime()).toEqual('11:11');
    // Test fails, as getTime returns undefined
});
 
test('App renders 11:11', () => {
    getTime.mockReturnValue('11:11')
    expect(getTime()).toEqual('11:11');
    // Test passes
});
 
test('App shows that it is morning', () => {
    expect(isMorning()).toEqual(false);
    // Test passes, as isMorning doesn't get reset
});
  • Since we have mocked getTime with jest.fn() the first test will fail because the mock has been reset and will return undefined
  • In the second test, since we explicitly mock the function, it passes
  • In the third test, since isMorning() has been directly mocked (it’s not using jest.fn()) the test will still pass as resetAllMocks doesn’t affect it.

When and how to use jest.spyOn()

Finally, let’s talk a little bit about jest.spyOn(). As the name might suggest it lets you “spy” on a function call. For example, you can assert to see what a function returns, or how many times it is called, without actually having to mock the function.

However it doesn’t work too well with ES6 modules, and to be honest I don’t use it at all. This is because its intended usage is with CommonJS’s require:

const time = require('./time');
const spy = jest.spyOn(time, 'getTime');

You see that you need to pass in two arguments to spyOn, one for the import object and one for the method name.

One workaround to get it working with ES6 is to import your module as an asterix import:

import * as time from './time';
const spy = jest.spyOn(time, 'getTime');

However you may run into TypeScript issues while using this.

Spying on functions with jest.mock()

If you wanted to replicate the behaviour of .spyOn() with ES6 module imports, you can use requireActual():

import { getTime } from './time';
 
jest.mock(() => {
    getTime: jest.fn(
        jest.requireActual('./time').getTime
    )
})

What this is doing here is:

  • We are mocking getTime() as you normally would with jest.mock() and `jest.fn()
  • But then on top of that, we have replaced the mock with the “real value” of the function by using Jest’s requireActual

It’s kind of confusing, but it works!

PS: Chaining mocks

As one final tip, when mocking multiple modules you can chain them like so:

jest
    .mock('./time', () => jest.fn())
    .mock('./space', () => jest.fn());

Recent posts

Comments