A guide to mocking functions and modules with Jest
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:
jest.mock()
and
jest.fn()
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:
// 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:
.mock()
at the top of your test file, and then mock your desired function with
jest.fn()
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:
mockReturnValueOnce()
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
});
getTime
with
jest.fn()
the first test will fail because the mock has been reset and will return
undefined
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