Photo by Becky Phan on Unsplash

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.

For JavaScript, there are great mocking libraries available like testdouble and sinon, and Jest provides mocking out of the box.

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 jest.fn().

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.

Dependency Injection

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.mock does this automatically for all functions in a module
  • jest.spyOn does 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 math.js to:

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 automock: true).

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 spyOn.

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 jest.spyOn.

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.

Conclusion

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 .

Hit me up on twitter, Stack Overflow, or our Discord channel for any questions!

Like what you read? Give Rick Hanlon II a round of applause.

From a quick cheer to a standing ovation, clap to show how much you enjoyed this story.