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:
- How to mock imported functions from a module using
jest.mock()
andjest.fn()
- When to use other Jest functions like
clearAllMocks
,resetAllMocks
andspyOn
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:
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()
:
Mocking a default import
If instead of importing a named import, you wanted to import a default import, you would mock it like this:
Mocking both default and named imports together
If you want to mock default and named imports together, you’ll need to include __esModule: true
:
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
:
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:
The key points to remember are that you must:
- Import the function you want to mock (on line 1).
- Mock the module with
.mock()
at the top of your test file, and then mock your desired function withjest.fn()
- 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:
You have couple options to get around this:
- Mocking only one call with
mockReturnValueOnce()
- 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.
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:
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:
We can clear the call count between each test by calling 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:
- Since we have mocked
getTime
withjest.fn()
the first test will fail because the mock has been reset and will returnundefined
- 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 asresetAllMocks
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
:
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:
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()
:
What this is doing here is:
- We are mocking
getTime()
as you normally would withjest.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: