Understanding Jest Mocks
Mocking is a technique to isolate test subjects by replacing dependencies with objects that you can control and inspect. A dependency can be anything your subject depends on, but it is typically a module that the subject imports.
Recently, I joined Jest as a collaborator to help triage the issue tracker, and I’ve noticed a lot of questions about how mocking in Jest works, so I thought I would put together a guide explaining it.
When we talk about mocking in Jest, we’re typically talking about replacing dependencies with the Mock Function. In this article we’ll review the Mock Function, and then dive into the different ways you can replace dependencies with it.
The Mock Function
The goal for mocking is to replace something we don’t control with something we do, so it’s important that what we replace it with has all the features we need.
The Mock Function provides features to:
- Capture calls
- Set return values
- Change the implementation
The simplest way to create a Mock Function instance is with
With this and Jest Expect, it’s easy to test the captured calls:
and we can change the return value, implementation, or promise resolution:
Now that we covered what the Mock Function is, and what you can do with it, let’s go into ways to use it.
One of the common ways to use the Mock Function is by passing it directly as an argument to the function you are testing. This allows you to run your test subject, then assert how the mock was called and with what arguments:
This strategy is solid, but it requires that your code supports dependency injection. Often that is not the case, so we will need tools to mock existing modules and functions instead.
Mocking Modules and Functions
There are three main types of module and function mocking in Jest:
jest.fn: Mock a function
jest.mock: Mock a module
jest.spyOn: Spy or mock a function
Each of these will, in some way, create the Mock Function. To explain how each of these does that, consider this project structure:
In this setup, it is common to test
app.js and want to either not call the actual
math.js functions, or spy them to make sure they’re called as expected. This example is trite, but imagine that
math.js is a complex computation or requires some IO you want to avoid making:
Mock a function with jest.fn
The most basic strategy for mocking is to reassign a function to the Mock Function. Then, anywhere the reassigned functions are used, the mock will be called instead of the original function:
This type of mocking is less common for a couple reasons:
jest.mockdoes this automatically for all functions in a module
jest.spyOndoes the same thing but allows restoring the original function
Mock a module with jest.mock
A more common approach is to use
jest.mock to automatically set all exports of a module to the Mock Function. So, calling
jest.mock('./math.js'); essentially sets
From here, we can use any of the above features of the Mock Function for all of the exports of the module:
This is the easiest and most common form of mocking (and is the type of mocking Jest does for you with
The only disadvantage of this strategy is that it’s difficult to access the original implementation of the module. For those use cases, you can use
Spy or mock a function with jest.spyOn
Sometimes you only want to watch a method be called, but keep the original implementation. Other times you may want to mock the implementation, but restore the original later in the suite.
In these cases, you can use
Here we simply “spy” calls to the math function, but leave the original implementation in place:
This is useful in a number of scenarios where you want to assert that certain side-effects happen without actually replacing them.
In other cases, you may want to mock a function, but then restore the original implementation:
This is useful for tests within the same file, but unnecessary to do in an
afterAll hook since each test file in Jest is sandboxed.
The key thing to remember about
jest.spyOn is that it is just sugar for the basic
jest.fn() usage. We can achieve the same goal by storing the original implementation, setting the mock implementation to to original, and re-assigning the original later:
In fact, this is exactly how
jest.spyOn is implemented.
In this article, we learned about the Mock Function and different strategies for re-assigning modules and functions in order to track calls, replace implementations, and set return values.
I hope this helped to simplify your understanding of Jest mocks so you can spend more time writing tests painlessly. For more info and best practices for mocking, check out this this 700+ 😱 slide talk titled Don’t Mock Me by Justin Searls .