How to mock imported functions with Jest
Tags: javascript, tutorial, testing, webdev
Published: 2021-08-03 18:42:41

> 📣 **Notice**
> This article is part of a series:
>
> **Testing JavaScript with Jest**
>
> To get the most out of this article, I recommend reading the previous article in the series:
>
> **[How to write unit tests in JavaScript with Jest](https://dev.to/dstrekelj/how-to-write-unit-tests-in-javascript-with-jest-2e83)**

---

Writing unit tests for code with dependencies can be difficult. This is especially true for large code bases where it's tricky to come up with fixtures that will cover all the cases we need to test.

But what if we could control the return value of a function dependency, no matter what arguments it is called with?

This is where mock functions come in.

Mock functions are a testing tool that allows us to track how function dependencies are called and control their return values. This makes it possible for us to manipulate the control flow of the tested program and reach even those difficult-to-reproduce edge-cases when writing tests.

This article will provide an introduction into the concepts behind mocking and how it relates to unit testing. We will learn how to mock functions and imported function modules with Jest, and write tests that rely on those mocks to increase the coverage of our test cases.

We will assume that we're testing a couple of validation rules:

```jsx
// isInteger.js
module.exports = (value) => Number.isSafeInteger(value);
```

```jsx
// isAtLeast18.js
const isInteger = require("./isInteger");

module.exports = (value) => isInteger(value) && value >= 18;
```

We want to see what our tests will teach us about the flaws in our code by passing and failing test cases. Fixing the implementation is not covered by this article, but feel free to play with it as we move through the article.

Read on to find out more!

---

## How to mock an imported function with Jest?

To mock an imported function with Jest we use the `jest.mock()` [function](https://jestjs.io/docs/jest-object#jestmockmodulename-factory-options).

`jest.mock()` is called with one required argument - the import path of the module we're mocking. It can also be called with an optional second argument - the factory function for the mock. If the factory function is not provided, Jest will automock the imported module.

> 💡**Note**
Jest **automock** is the automatic mocking of imported modules with surface-level replacement implementations. Automocking is disabled by default since [Jest 15](https://jestjs.io/blog/2016/09/01/jest-15#disabled-automocking), but can be enabled by configuring Jest with the `automock` [flag](https://jestjs.io/docs/configuration#automock-boolean).

When testing `isAtLeast18()` we have to keep in mind that the `isInteger()` dependency affects the module's behaviour:

1. if `isInteger()` is `false` , `isAtLeast18()` is `false`;
2. if `isInteger()` is `true` , `isAtLeast18()` depends on the value argument.

We'll start by testing the case of `isInteger()` returning `false`. 

The `isInteger.js` module has a single default export - the `isInteger()` function. We will mock the imported module with a factory function that behaves just like the default export and returns a function. That function, when called, will always return `false`.

```jsx
// isAtLeast18.spec.js
const isAtLeast18 = require("./isAtLeast18");

// The mock factory returns the function () => false
jest.mock("./isInteger", () => () => false);

describe("isAtLeast18", () => {
	it("fails if value is not recognised as integer", () => {
		// Should pass, but fails because of the isInteger() mock
		expect(isAtLeast18(123)).toBe(false);
		// Should fail either way
		expect(isAtLeast18("abc")).toBe(false);
	});
});
```

> 💡**Note**
The import path of the mocked module must match the import path that is present in the module we're testing. The `isAtLeast18.js` module imports the `isInteger.js` module under the path "./isInteger". This is why our mock import path is also "./isInteger".

`isAtLeast18()` will now always return `false` no matter what we call it with, because the `isInteger()` mock is set to always return `false`.

But what about the case when `isInteger()` returns `true`?

To mock different return values depending on the test we will create a mock function.

> 💡**Note**
This unit test is a solitary unit test because the tested unit is isolated from its dependencies. Read more about solitary unit tests in the previous article: [How to write unit tests in JavaScript with Jest](https://dev.to/dstrekelj/how-to-write-unit-tests-in-javascript-with-jest-2e83).

---

## What is a mock function?

A **mock function** is a function that replaces the actual implementation of a function with a "fake" (mock) implementation.

Mock functions track how they are called by external code. With a mock function we can know the number of times the function was called, the arguments it was called with, the result it returned, and more. This ability to "spy" on function calls is why mock functions are also called **spies**.

We use mock functions to override original function behaviour with custom mock implementations. Mock implementations help us control the return values of a function. This makes our tests more predictable (deterministic) and easier to write.

---

## How to mock a function with Jest?

To mock a function with Jest we use the `jest.fn()` [function](https://jestjs.io/docs/jest-object#jestfnimplementation).

`jest.fn()` can be called with an implementation function as an optional argument. If an implementation is provided, calling the mock function will call the implementation and return it's return value.

If no implementation is provided, calling the mock returns `undefined` because the return value is not defined.

```jsx
// Without implementation, this mock returns `undefined`.
const mockUndefined = jest.fn();

// With implementation, this mock returns `true`.
const mockTrue = jest.fn(() => true).
```

Jest registers mock functions under the "jest.fn()" name by default. We can give the mock function a custom name with the `mockName()` [method](https://jestjs.io/docs/mock-function-api#mockfnmocknamevalue). The mock name is used when printing test results.

```jsx
const mockOne = jest.fn(() => false);
// Example error: expect(jest.fn()).toHaveBeenCalledWith(...expected)

const mockTwo = jest.fn(() => false).mockName('mockTwo');
// Example error: expect(mockTwo).toHaveBeenCalledWith(...expected)
```

> 💡**Note**
It's good practice to name mocked functions in cases where a lot of different mocks are used. This makes it easier to tell mocked functions apart and debug code that isn't matching expectations.

---

## How to change the mock implementation of a function with Jest?

To change the mock implementation of a function with Jest we use the `mockImplementation()` [method](https://jestjs.io/docs/mock-function-api#mockfnmockimplementationfn) of the mocked function.

The `mockImplementation()` method is called with the new implementation as its argument. The new implementation will then be used in place of the previous one when the mock is called.

```jsx
// The initial mock is a function that returns `true`.
const myMock = jest.fn(() => true);

// The new mock implementation has the function return `false`.
myMock.mockImplementation(() => false);
```

We can combine this with `jest.mock()` factory functions to create mocked modules that contain mocked functions. This way we can control how the implementation of a mock behaves depending on what we're testing.

```jsx
// isAtLeast18.spec.js
const isAtLeast18 = require("./isAtLeast18");
const isInteger = require("./isInteger");

// The mock factory returns a mocked function
jest.mock("./isInteger", () => jest.fn());

describe("isAtLeast18", () => {
	it("fails if value is not recognised as integer", () => {
		// For this test we'll mock isInteger to return `false`
		isInteger.mockImplementation(() => false);

		expect(isAtLeast18(123)).toBe(false);
		expect(isAtLeast18("abc")).toBe(false);
	});

	it("passes if value is recognised as integer and is at least 18", () => {
		// For this test we'll mock isInteger to return `true`
		isInteger.mockImplementation(() => true);

		expect(isAtLeast18(123)).toBe(true);
		expect(isAtLeast18("abc")).toBe(false);
	});
});
```

> 💡**Note**
`jest.mock()` works by modifying the Node module cache to give us the mock instead of the original implementation whenever we import a mocked module in a test file. To support ES module imports - where `import` statements have to come first in a file - Jest [automatically hoists](https://jestjs.io/docs/manual-mocks#using-with-es-module-imports) `jest.mock()` calls to the top of the module. Read more about this technique [here](https://tech.julienkarst.com/How-to-mock-a-javascript-module-with-jest).

---

## How to check if a function was called correctly with Jest?

To check if a function was called correctly with Jest we use the `expect()` [function](https://jestjs.io/docs/expect#expectvalue) with specific matcher methods to create an assertion.

We can use the `toHaveBeenCalledWith()` [matcher method](https://jestjs.io/docs/expect#tohavebeencalledwitharg1-arg2-) to assert the arguments the mocked function has been called with.

To assert how many times the mocked function has been called so far, we can use the `toHaveBeenCalledTimes()` [matcher method](https://jestjs.io/docs/expect#tohavebeencalledtimesnumber).

```jsx
// isAtLeast18.spec.js
const isAtLeast18 = require("./isAtLeast18");
const isInteger = require("./isInteger");

jest.mock("./isInteger", () => jest.fn());

describe("isAtLeast18", () => {
  it("fails if value is not recognised as integer", () => {
    isInteger.mockImplementation(() => false);

    expect(isAtLeast18(123)).toBe(false);

		// We expect isInteger to be called with 123
    expect(isInteger).toHaveBeenCalledWith(123);
		// We expect isInteger to be called once
    expect(isInteger).toHaveBeenCalledTimes(1);
  });
});
```

> 💡**Note**
While these are the most common matcher methods for functions, there are more matcher methods available in the [Jest API docs](https://jestjs.io/docs/expect#tohavebeencalled).

Jest tracks all calls to mocked functions. A mocked function will remember the [arguments and times](https://jestjs.io/docs/mock-function-api#mockfnmockcalls) it has been called, as well as the [results](https://jestjs.io/docs/mock-function-api#mockfnmockresults) of those calls.

When reusing mocked functions between tests it is useful to reset their states before running new tests to get a clear baseline. We can do that by clearing mocked functions between tests.

---

## How to clear mocked functions with Jest?

To clear mocked functions with Jest we use the `mockClear()` [method](https://jestjs.io/docs/mock-function-api#mockfnmockclear) of a mocked function.

`mockClear()` resets all information stored in mocked functions which makes it useful for cleaning up a mock's usage data between assertions or tests.

```jsx
// isAtLeast18.spec.js
const isAtLeast18 = require("./isAtLeast18");
const isInteger = require("./isInteger");

jest.mock("./isInteger", () => jest.fn());

describe("isAtLeast18", () => {
  it("fails if value is not recognised as integer", () => {
    isInteger.mockImplementation(() => false);

    expect(isAtLeast18(123)).toBe(false);
    expect(isInteger).toHaveBeenCalledWith(123);
    expect(isInteger).toHaveBeenCalledTimes(1);

		// Clear the mock so the next test starts with fresh data
    isInteger.mockClear();
  });

  it("passes if value is recognised as integer and is at least 18", () => {
    isInteger.mockImplementation(() => true);

    expect(isAtLeast18(123)).toBe(true);
    expect(isInteger).toHaveBeenCalledWith(123);
		// Without clearing, there would be 2 calls total at this point
    expect(isInteger).toHaveBeenCalledTimes(1);
  });
});
```

> 💡**Note**
Jest also provides [mock function methods](https://jestjs.io/docs/mock-function-api) for resetting and restoring mocked functions, as well as shorthands for creating mocked functions that directly return, resolve, or reject a value.

---

## How to clear mocked functions before each test with Jest?

To clear mocked functions before each test with Jest we use the `beforeEach()` [function](https://jestjs.io/docs/api#beforeeachfn-timeout).

`beforeEach()` is called with one required argument - the function to run before each of the tests in the test file. We use it to clear mocks, set up fixtures, or reset some other state used across tests.

```jsx
// isAtLeast18.spec.js
const isAtLeast18 = require("./isAtLeast18");
const isInteger = require("./isInteger");

jest.mock("./isInteger", () => jest.fn());

// Clear mock data before each test
beforeEach(() => {
  isInteger.mockClear();
});

describe("isAtLeast18", () => {
  it("fails if value is not recognised as integer", () => {
    isInteger.mockImplementation(() => false);

    expect(isAtLeast18(123)).toBe(false);
    expect(isInteger).toHaveBeenCalledWith(123);
    expect(isInteger).toHaveBeenCalledTimes(1);
  });

  it("passes if value is recognised as integer and is at least 18", () => {
    isInteger.mockImplementation(() => true);

    expect(isAtLeast18(123)).toBe(true);
    expect(isInteger).toHaveBeenCalledWith(123);
    expect(isInteger).toHaveBeenCalledTimes(1);
  });
});
```

> 💡**Note**
Jest provides four [functions](https://jestjs.io/docs/api#reference) to hook into the set-up and tear-down process, both before and after each or all of the tests in a test file. These functions are: `afterAll()`, `afterEach()`, `beforeAll()`, `beforeEach()`. The `afterAll()` and `beforeAll()` variants are called only once for the entire test file. The `afterEach()` and `beforeEach()` variants are called once for every test in the test file.

---

## How to reuse mocks with Jest?

To reuse mocks with Jest we create mocks in a `__mocks__/` [subdirectory](https://jestjs.io/docs/manual-mocks#mocking-user-modules) adjacent to the module we want to mock.

Mock files in the `__mocks__/` subdirectory are used to automock the modules they are adjacent to when the module is mocked with `jest.mock()`. This is useful when dealing with a lot of repetition in setting up mocks such as when mocking common dependencies or configuration objects because it makes writing a mock factory function unnecessary.

Assuming a common configuration file that is used by many different modules, mocking it would look like this:

```jsx
// common/config.js
module.exports = { foo: "bar" };
```

```jsx
// common/__mocks__/config.js
module.exports = { foo: "mockBar" };
```

```jsx
// example.js
const config = require.("./common/config");

// Logs "bar"
module.exports = () => console.log(config.foo);
```

```jsx
// example.spec.js
const example = require("./example");

jest.mock("./common/config");

// Logs "mockBar", no need for a mock factory
example();
```

> 💡**Note**
Remember: mocks and automocking are only in effect when running tests with Jest. They do not have an effect on the code in development or production.

That's it! We're now ready to mock imported functions with Jest.

---

## Jest mock imported functions example code

The dependency in `isInteger.js`:

```jsx
// isInteger.js
module.exports = (value) => Number.isSafeInteger(value);
```

The unit to be tested in `isAtLeast18.js`:

```jsx
// isAtLeast18.js
const isInteger = require("./isInteger");

module.exports = (value) => isInteger(value) && value >= 18;
```

The unit test in `isAtLeast18.spec.js`:

```jsx
// isAtLeast18.spec.js
const isAtLeast18 = require("./isAtLeast18");
const isInteger = require("./isInteger");

// The mock factory returns a mocked function
jest.mock("./isInteger", () => jest.fn());

beforeEach(() => {
  isInteger.mockClear();
});

describe("isAtLeast18", () => {
  it("fails if value is not recognised as integer", () => {
    isInteger.mockImplementation(() => false);

    expect(isAtLeast18(123)).toBe(false);
    expect(isInteger).toHaveBeenCalledWith(123);
    expect(isInteger).toHaveBeenCalledTimes(1);
  });

  it("passes if value is recognised as integer and is at least 18", () => {
    isInteger.mockImplementation(() => true);

    expect(isAtLeast18(123)).toBe(true);
    expect(isInteger).toHaveBeenCalledWith(123);
    expect(isInteger).toHaveBeenCalledTimes(1);
  });
});
```

---

## Homework and next steps

- Write more comprehensive tests and use fixtures to cover any additional cases. If you've done your homework from the previous article, try continuing from where you left off.
- Fix the code so any failed tests pass or write a newer, better implementation.
- Achieve 100% code coverage in the coverage report.

---

Thank you for taking the time to read through this article!

Have you tried mocking imported functions with Jest before? What was your experience like?

Leave a comment and start a discussion!