Spies and mocking with Jest
Tags: javascript, jest, node, testing
Published: 2023-01-11 12:42:34

---
title: Spies and mocking with Jest
published: true
tags: ['javascript', 'jest', 'node', 'testing']
canonical_url: https://sevic.dev/notes/spies-mocking-jest/
---

Besides asserting the output of the function call, unit testing includes the usage of spies and mocking.

Spies are functions that let you spy on the behavior of functions called indirectly by some other code. Spy can be created by using `jest.fn()`.

Mocking injects test values into the code during the tests.

Some of the use cases will be presented below.

- Async function and its resolved value can be mocked using `mockResolvedValue`. Another way to mock it is by using `mockImplementation` and providing a function as an argument.

```js
const calculationService = {
  calculate: jest.fn()
};

jest.spyOn(calculationService, 'calculate').mockResolvedValue(value);

jest
  .spyOn(calculationService, 'calculate')
  .mockImplementation(async (a) => Promise.resolve(a));
```

- Rejected async function can be mocked using `mockRejectedValue` and `mockImplementation`.

```js
jest
  .spyOn(calculationService, 'calculate')
  .mockRejectedValue(new Error(errorMessage));

jest
  .spyOn(calculationService, 'calculate')
  .mockImplementation(async () => Promise.reject(new Error(errorMessage)));

await expect(calculateSomething(calculationService)).rejects.toThrowError(
  Error
);
```

- Sync function and its return value can be mocked using `mockReturnValue` and `mockImplementation`.

```js
jest.spyOn(calculationService, 'calculate').mockReturnValue(value);

jest.spyOn(calculationService, 'calculate').mockImplementation((a) => a);
```

- Chained methods can be mocked using `mockReturnThis`.

```js
// calculationService.get().calculate();
jest.spyOn(calculationService, 'get').mockReturnThis();
```

- Async and sync functions called multiple times can be mocked with different values using `mockResolvedValueOnce` and `mockReturnValueOnce`, respectively, and `mockImplementationOnce`.

```js
jest
  .spyOn(calculationService, 'calculate')
  .mockResolvedValueOnce(value)
  .mockResolvedValueOnce(otherValue);

jest
  .spyOn(calculationService, 'calculate')
  .mockReturnValueOnce(value)
  .mockReturnValueOnce(otherValue);

jest
  .spyOn(calculationService, 'calculate')
  .mockImplementationOnce((a) => a + 3)
  .mockImplementationOnce((a) => a + 5);
```

- External modules can be mocked similarly to spies. For the following example, let's suppose `axios` package is already used in one function. The following example represents a test file where `axios` is mocked using `jest.mock()`.

```js
import axios from 'axios';
jest.mock('axios');

// within test case
axios.get.mockResolvedValue(data);
```

- Manual mocks are resolved by writing corresponding modules in `__mocks__` directory, e.g., `fs/promises` mock will be stored in `__mocks__/fs/promises.js` file. `fs/promises` mock will be resolved using `jest.mock()` in the test file.

```js
jest.mock('fs/promises');
```

- To assert called arguments for a mocked function, an assertion can be done using `toHaveBeenCalledWith` matcher.

```js
const spy = jest.spyOn(calculationService, 'calculate');

expect(spy).toHaveBeenCalledWith(firstArgument, secondArgument);
```

- To assert skipped call for a mocked function, an assertion can be done using `not.toHaveBeenCalled` matcher.

```js
const spy = jest.spyOn(calculationService, 'calculate');

expect(spy).not.toHaveBeenCalled();
```

- To assert how many times mocked function is called, an assertion can be done using `toHaveBeenCalledTimes` matcher.

```js
const spy = jest.spyOn(calculationService, 'calculate');

calculationService.calculate(3);
calculationService.calculate(2);

expect(spy).toHaveBeenCalledTimes(2);
```

- To assert called arguments for the exact call when a mocked function is called multiple times, an assertion can be done using `toHaveBeenNthCalledWith` matcher.

```js
const argumentsList = [0, 1];

argumentsList.forEach((argumentList, index) => {
  expect(calculationService.calculate).toHaveBeenNthCalledWith(
    index + 1,
    argumentList
  );
});
```

- Methods should be restored to their initial implementation before each test case.

```json
// package.json
"jest": {
  // ...
  "restoreMocks": true
}
// ...
```

### Demo

The demo with the mentioned examples is available [here](https://sevic.dev/demos).