Mocks and Spies with Jest
Tags: javascript, typescript, testing
Published: 2020-12-31 16:53:32

When writing unit test, you often have to mock some functions. It can be to make your tests deterministic, or to assert that a specific function gets called. Let's imagine you are trying to assess that your function calls the right API using fetch.

```js
async function getUser(id) {
  return fetch(`/users/${id}`)
}
```

Let's see the different options we have when trying to assert this.

## Using jest.fn

The first way to achieve it is to use directly `jest.fn` and replace the fetch function.

```js
describe('my test', () => {
  it('calls the right route', async () => {
    // Create an empty mock function which just returns nothing
    const mockedFetch = jest.fn()
    // Set the global fetch to be this function
    global.fetch = mockedFetch
    await getUser(id)
    expect(mockedFetch).toHaveBeenCalledWith('/users/12')
  })
}
```

This will work, but there are some drawbacks. The biggest one being: you will have to manually keep a reference to the actual fetch method, and put it back in place after the test. If this fails, it will impact all the other tests in your test suite.

You could do it this way:
```js
describe('my test', () => {
  const realFetch = global.fetch
  beforeAll(() => {
    global.fetch = jest.fn()
  })

  afterAll(() => {
    global.fetch = realFetch
  })
}
```

## Using jest.spyOn

A better approach in this case would be to use a spy. Spies have all the features of a mock function, but leave you with more flexibility. More importantly, Jest will handle the cleaning of mocks for you. Here is what the test look like using spies:

```js
describe('my test', () => {
  it('calls the right route', async () => {
    jest.spyOn(global, 'fetch')
    await getUser(id)
    expect(global.fetch).toHaveBeenCalledWith('/users/12')
  })
}
```

There are two things to remember about spies:
* You still need to tell Jest to forget about the mock between tests using `mockClear`, `mockReset` or `mockRestore` (more on that later)
* By default it just spies on the function and does not prevent the original code to be executed.

If we wanted to fix these 2 behaviours, the test would look like this:
```js
describe('my test', () => {
  beforeEach(() => {
    jest.restoreAllMocks()
  })

  it('calls the right route', async () => {
    jest.spyOn(global, 'fetch').mockReturnValue({})
    await getUser(id)
    expect(global.fetch).toHaveBeenCalledWith('/users/12')
  })
}
```

Here we prevented the actual call to fetch using `mockReturnValue` (`mockImplementation` can be used too), and we restore all existing mocks to their initial state before every test run.

## Clear, reset and restore

When clearing mocks, you have 3 possible functions you can call:
* `mockClear` - clearing a mock means clearing the history of calls that already have been stored in the mock. It can be useful if you want to start counting calls after some point in your test.
* `mockReset` - reseting a mock returns the mock to a fresh state, just like if you just called `spyOn` on the function. All mocked implementation or return value will be forgotten. Of course it also implies everything `mockClear` implies.
* `mockRestore` - Restoring the function actually removes the mock, and restore the original implementation.

All these functions can be used in two different ways:
* Directly on a mocked function: `myMockedFunction.mockClear`
* Globally to affect all existing mocks you've created: `jest. clearAllMocks()`

## Using jest.mock

Another approach to mocking with Jest is to use `jest.mock`. It allows you to mock entirely a module. For instance:

```js
// Here I am mocking the 'os' module entirely
// It now only exposes one function: hostname
// Which always returns 'my-computer'
jest.mock('os', () => {
  return { hostname: () => 'my-computer' }
})
```

As you can see, you can use the second parameter to provide a factory so that the import return something. If you don't importing the module will just return an empty object.

It is also possible to write this factory in a specific file. For instance if you were to import the file `src/my-module.js`, and wanted a specific factory for it in every test, you can create a file named `src/__mocks__/my-module.js`. Whatever this file exports if what will be imported when calling `jest.mock('src/my-module')` without a factory provided.

## So what do I use now?

As much as possible, try to go with the `spyOn` version.

Using `jest.fn` directly have a few use cases, for instance when passing a mocked callback to a function.

`jest.mock` is powerful, but I mostly use it to prevent loading a specific module (like something that needs binaries extensions, or produces side effects). I also use it when I need to mock a lot of functions from an external module at once.

___________

Photo by [Tobias Tullius](https://unsplash.com/@tobiastu?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText) on [Unsplash](https://unsplash.com/?utm_source=unsplash&amp;utm_medium=referral&amp;utm_content=creditCopyText)