Introduction to Jest mocking (Jest mocking + React part 1)
Tags: jest, mocking, react
Published: 2022-09-08 14:46:20

This series is an introduction into mocking with `Jest`. You don't need any prior knowledge about mocking to be able to follow this but you should at least know what a `Jest` test is and how to set one up. I use a lot of examples to make everything as clear as I can but it remains a hefty read.

This first part explains what mocking is, why you need it and how to setup mocking functions. The examples use plain old javascript.

1. What is a Jest mock?
2. Relevant matcher functions
3. Adding return values to mocks

In the next parts we apply what we learned to test `React` components.

*The examples I use in this article are available on [github](https://github.com/peterlidee/mocking-app) ([src/part1](https://github.com/peterlidee/mocking-app/tree/main/src/part1)). These files a build upon create-react-app so you can run them using `npm start` or run the tests using `npm test`.*


## 1. What is a Jest mock?

A mock is a simulated function that records or logs it's own behaviour. It is a simulation because it doesn't actually do anything, it doesn't have a function body. A mock can take arguments and return values but there is nothing in between. Hence, it is a simulation.

A mock also 'logs' it's behaviour. This means that for each time a mock is called, it saves the arguments it was called with as well as the values it returned. As a tester, you can access these 'logs' and use them in your tests. I will use an example to make this clear.

```javascript
// part1/doSomething.js
function doSomething(callback){
  const list = ['foo', 'bar']
  list.forEach(listItem => callback(listItem))
}
export default doSomething
```

We have a little function `doSomething()` that takes a function/callback as an argument. When it runs, it calls that callback on each item in the list array.

```javascript
function callback(item){
  console.log(item)
}
doSomething(callback)
// prints in console: 
// 'foo'
// 'bar'
```

We want to test the `doSomething()` function but we don't have controle over the callback function. It might be anything. However, what we do know is **how the callback should behave**:

- Callback should be called twice.
- Callback should be called the first time with one argument 'foo'.
- Callback should be called the second time with one argument 'bar'.

Instead of using an actual callback function, we will use a mock. This mock allows us to make these assertions. So, mocking lets you test a function without an actual callback. And that is exactly what we want. Here is the test:

```javascript
// part1/__tests__doSomething.test.js
test('doSomething callback behaves correctly', () => {
  const mockCallback = jest.fn()
  doSomething(mockCallback)
  expect(mockCallback.mock.calls).toHaveLength(2)
  expect(mockCallback.mock.calls[0][0]).toBe('foo')
  expect(mockCallback.mock.calls[1][0]).toBe('bar')
})
```
Let's break this test down. First we create a new mock. A mock is created by simply calling `jest.fn()`. We then run the `doSomething()` function passing in the mock: `doSomething(mockCallback)`. 

`doSomething()` takes the mock function as a callback and then calls each item in the list array with this callback: `callback('foo')`, `callback('bar')`.

We used a mock function. Mock functions log what happens to them, how they are used. How do we access this log? We take the mock we made (`mockCallback`) and look for a `.mock` property: `mockCallback.mock`. This might be a bit confusing, the mock `mockCallback` has a property `.mock` but that's just how it is.

This mock property is an object and it's two main properties are `.calls` and `.results`. The calls property is a 'log' of what the mock function was called with. The results property is a 'log' of the return values of the callback (more on that later). If you console.log `mockCallback.mock` after it was called by `doSomething()` it would look like this:

```javascript
// console.log(mockCallback.mock)
{
  calls: [
    ['foo'],
    ['bar'],
  ],
  results: [
    { type: 'return', value: undefined },
    { type: 'return', value: undefined },
  ],
  // ... more props
}
```
`mockCallback` was called twice, so both `.calls` and `.results` have 2 items. The first item in the calls array represent the first call, the second item represents the second call. Same goes for the results array.

`mockCallback.mock.calls[0]` is in itself again an array representing the number of arguments. Since we called `mockCallback` with only one argument 'foo', the array has only one item. Had `mockCallback` been called with more arguments, it would look like this: `calls[0] = ['argument1', 'argument2', ...]`.

Now, let's look back at the `expect()` statements in the above test. These should make sense now.

```javascript
// part1/__tests__/doSomething.test.js
test('doSomething callback behaves correctly', () => {
  const mockCallback = jest.fn()
  doSomething(mockCallback)
  expect(mockCallback.mock.calls).toHaveLength(2)
  expect(mockCallback.mock.calls[0][0]).toBe('foo')
  expect(mockCallback.mock.calls[1][0]).toBe('bar')
})
```
- `expect(mockCallback.mock.calls).toHaveLength(2)` because `mockCallback` was called twice.
- `expect(mockCallback.mock.calls[0][0]).toBe('foo')`: we expect that the **first** time `mockCallback` was called, it's first argument was 'foo'.
- `expect(mockCallback.mock.calls[1][0]).toBe('bar')`: we expect that the **second** time `mockCallback` was called, it's first argument was 'bar'.

Let's go over this whole mocking thing once more. A mock is a simulation of a function that logs it's own behaviour. 
- It's a simulation of a function because in itself, a mock doesn't do anything. 
- It logs it's behaviour: for each time a mock is called, it saves both its arguments and results.

To make a mock you just use `const myMock = jest.fn()`. You can access the arguments on `myMock.mock.calls` and the return values on `myMock.mock.results`.

___

## 2. Relevant matcher functions

Checking `mockFunction.mock.calls[1][0]` is a bit messy. Luckily, `Jest` has some helpers for this. Here are a few:

- `.toHaveBeenCalled()`: the mock was called at least once.
- `.toHaveBeenCalledTimes()`: how many times the mock was called.
- `.toHaveBeenCalledWith()`: the mock was called with these arguments at least once.
- `.toHaveBeenNthCalledWith()`: the mock was called with these arguments the nth time it got called.
- `.toHaveBeenLastCalledWith()`: the mock was called with these arguments the last time it got called.

See them in action on our `doSomething()` example:

```javascript
// part1/__tests__/doSomething.test.js
test('doSomething with Jest matchers', () => {
  const mockCallback = jest.fn()
  doSomething(mockCallback)

  expect(mockCallback).toHaveBeenCalled()
  expect(mockCallback).toHaveBeenCalledTimes(2)
  // this is not the recommended method when testing multiple calls
  expect(mockCallback).toHaveBeenCalledWith('foo')
  expect(mockCallback).toHaveBeenCalledWith('bar')
  // this is the recommended method when testing multiple calls
  expect(mockCallback).toHaveBeenNthCalledWith(1, 'foo')
  expect(mockCallback).toHaveBeenNthCalledWith(2, 'bar')
  expect(mockCallback).toHaveBeenLastCalledWith('bar')
})
```
As you can see, these `Jest` matchers are a lot cleaner. But, the `.mock` property on the mock function is still important:

- There will be situation where you need to use it.
- I feel it gives a deeper understanding how mocks work internally.

___

## 3. Adding return values to mocks

Mock function are simulations that don't return any value. But what do you do when you actually need them to return something? For example when the function you are testing relies on the mocked function to get data. This section covers how to add return values to mocks.

A quick remark first: all javascript functions that have no explicit return, return `undefined`. The same goes for mocks. Let's test this:

```javascript
// part1/__tests__doSomething.test.js
test('doSomething with no return value', () => {
  const mockCallback = jest.fn()
  doSomething(mockCallback)
  expect(mockCallback.mock.results[0].value).toBeUndefined()
  expect(mockCallback.mock.results[1].value).toBeUndefined()
})
```

Before we add return values to our mocks, I'm gonna introducer some matcher functions to **test return values**. We will use these to test the return values on our mocks. They should look familiar:

- `.toHaveReturned()`
- `.toHaveReturnedTimes(number)`
- `.toHaveReturnedWith(value)`
- `.toHaveLastReturnedWith(value)`
- `.toHaveNthReturnedWith(nthCall, value)`

There are a number of ways to add return values to mock function. The simplest one is to call `jest.fn()` with a function as argument. This function is called a mock implementation parameter: `jest.fn(implementation)` because it's a function that returns a mock. Our example will make this clear:

```javascript
// part1/__tests__/doSomething.test.js
test('doSomething with mock implementation parameter', () => {
  // pass mock implementation function into jest.fn
  const mockCallback = jest.fn(() => 'Hello')
  doSomething(mockCallback)

  // using .mock property
  expect(mockCallback.mock.results[0].value).toBe('Hello')
  // equivalent using matcher
  expect(mockCallback).toHaveNthReturnedWith(1, 'Hello')
  // using .mock property
  expect(mockCallback.mock.results[1].value).toBe('Hello')
  // equivalent using matcher
  expect(mockCallback).toHaveNthReturnedWith(2, 'Hello')
})

```

Our implementation is just an anonymous arrow function that we pass into `jest.fn()`. Every time `mockCallback` is called, it returns 'Hello'. 

In our `doSomething()` function, our mocks get called with arguments: 'foo' and 'bar'. This is how we catch those values:

```javascript
// part1/__tests__/doSomething.test.js
test('doSomething with mock implementation parameter that catches the values passed into mockCallback', () => {
  const mockCallback = jest.fn((value) => 'Hello ' + value)
  doSomething(mockCallback)
  expect(mockCallback).toHaveNthReturnedWith(1, 'Hello foo')
  expect(mockCallback).toHaveNthReturnedWith(2, 'Hello bar')
})
```
More ways to return values from mock are provided by `Jest` helpers. When you create a `jest.fn()` you get access to a [series of methods](https://jestjs.io/docs/mock-function-api#methods). Some of these methods help you with returning values from mocks. We start with `.mockReturnValue(value)`.

```javascript
// part1/__tests__/doSomething.test.js
test('doSomething with .mockReturnValue() on mock', () => {
  const mockCallback = jest.fn()
  mockCallback.mockReturnValue('Hello')
  // you can chain these methods
  // const mockCallback = jest.fn().mockReturnValue('Hello')
  doSomething(mockCallback)
  expect(mockCallback).toHaveNthReturnedWith(1, 'Hello')
  expect(mockCallback).toHaveNthReturnedWith(2, 'Hello')
})
```
Then there is `.mockReturnValueOnce()`. This one is a bit weird because there are some edge cases. We will use a new example function `doSomethingElse()` to demonstrate. This new function is the same as `doSomething()` except for other and more items in the list array.

```javascript
// part1/doSomethingElse.js
// instead of 'foo' and 'bar' we call the mock 4 times now
function doSomethingElse(callback){
  const list = ['a', 'b', 'c', 'd']
  list.forEach(item => callback(item))
}
```
The test:

```javascript
// part1/__tests__/doSomethingElse.test.js
test('doSomethingElse with .mockReturnValueOnce() on mock', () => {
  const mockCallback = jest
    .fn()
    .mockReturnValue('default')
    .mockReturnValueOnce('value 1')
    .mockReturnValueOnce('value 2')
  // call the function we are testing
  doSomethingElse(mockCallback)

  // test the return values
  expect(mockCallback).toHaveNthReturnedWith(1, 'value 1')
  expect(mockCallback).toHaveNthReturnedWith(2, 'value 2')
  expect(mockCallback).toHaveNthReturnedWith(3, 'default')
  expect(mockCallback).toHaveNthReturnedWith(4, 'default')
})
```

A breakdown: first we setup our `jest.fn()` and then we chain on our methods. We chained two `.mockReturnValueOnce()` after `.mockReturnValue()`. Yet, `.mockReturnValueOnce()` will be called first. This is what the test demonstrates. The once methods are called first and only once. 

If you omit `.mockReturnValue()` then the third and fourth mock calls would just return `undefined`. So, `.mockReturnValueOnce()` allows you to setup different return values for each time the mock is called.

The next two methods `.mockImplementation()` and `.mockImplementationOnce()` allow you to setup a return function (once). As you would expect, these methods take a function as argument.

```javascript
// part1/__tests__/doSomething.test.js
test('doSomething with .mockImplementation() on mock', () => {
  const mockCallback = jest
    .fn()
    .mockImplementation((value) => 'Hello ' + value)
  doSomething(mockCallback)
  expect(mockCallback).toHaveNthReturnedWith(1, 'Hello foo')
  expect(mockCallback).toHaveNthReturnedWith(2, 'Hello bar')
})
```

They are the equivalent of `jest.fn(value => 'Hello ' + value)`. But `.mockImplementationOnce()` gives you more options. Also note that after an expect statement you can set a new `.mockImplementation()` statement on your mock and perform a new test. Just mentioning this in case you would need this.

___

## Summary

`Jest` mocking functions are simulated functions that log their own behaviour. As a tester, you can access these logs to make assertions. 

Mock functions can be customized to return the values. There are different ways to do this and they make mock functions very flexible.

This article only tested pure javascript functions. In the [next articles](https://dev.to/peterlidee/mocking-react-components-jest-mocking-react-part-2-2l8j) in this series I'm gonna show you how to use `Jest` mocking to test `React` components.