Jest mocking strategies
Tags: javascript, jest, testing, mocking
Published: 2020-07-13 01:14:15

_This post was last updated on July 12, 2020. Always refer to library documentation for the most updated information._

_Note: this post assumes familiarity with Jest and mocking. If you want to learn more, take a look at [the Jest docs](https://jestjs.io/docs/en/mock-functions) first_ 🙂

## Table of contents
1. [ES6 Exports](#es6-exports)
  - [Default export of a vanilla function](#default-export-of-a-vanilla-function)
  - [Named export of a vanilla function](#named-export-of-a-vanilla-function)
  - [Default export of an object](#default-export-of-an-object)
  - [Named export of an object](#named-export-of-an-object)
  - [Default export of a function that returns an object](#default-export-of-a-function-that-returns-an-object)
  - [Named export of a function that returns an object](#named-export-of-a-function-that-returns-an-object)
2. [Mock behavior](#mock-behavior)
  - [Browser functionality](#browser-functionality)
      - [For the whole test suite](#for-the-whole-test-suite)
      - [In a single file](#in-a-single-file)
  - [Node modules](#node-modules)
      - [For the whole test suite](#for-the-whole-test-suite-2)
      - [In a single file](#in-a-single-file-2)
  - [A single function of a node module](#a-single-function-of-a-node-module)
      - [For the whole test suite](#for-the-whole-test-suite-3)
      - [In a single file](#in-a-single-file-3)
      - [In a single test](#in-a-single-test)
3. [Common mocking errors](#common-mocking-errors)
  - [The module factory of `jest.mock()` is not allowed to reference any out-of-scope variables](#the-module-factory-of-jest.mock()-is-not-allowed-to-reference-any-out-of-scope-variables)
  - [Cannot spy the default property because it is not a function](#cannot-spy-the-default-property-because-it-is-not-a-function)
  - [Cannot set property of \#\<Object\> which has only a getter](#cannot-set-property-of-%23%3Cobject%3E-which-has-only-a-getter)
  - [Warning: An update inside a test was not wrapped in act](#warning%3A-an-update-inside-a-test-was-not-wrapped-in-act)

Recently, I've been spending more time wrestling with uncooperative mocks than writing the code or the tests combined. I created this post to serve as an easily navigable guidebook of strategies for the next time `jest.mock('modulename')` won't cut it. This is not an exhaustive list, there are multiple ways to satisfy every use case.

When mocking a module or function, there are 2 main things to consider:

> 1. How was the module exported?
> 2. What do we want the behavior of our mock to be?

**All of the following code samples can be [found on my Github](https://github.com/mercedesb/jest-mocking-strategies).** The sample application shows you a random cute image of an animal every 3 seconds. It's a [React](https://reactjs.org/) app with [Jest](https://jestjs.io/) as the test runner and uses [React Testing Library](https://testing-library.com/docs/react-testing-library/intro) to test the component DOM (this is the default configuration with [Create React App](https://github.com/facebook/create-react-app)).

Despite being built with React, the mocking examples should be easily portable to any framework.

## ES6 Exports

Before we worry about our mock's behavior, it's important to understand how the package we're using is exported.

When publishing a package, the maintainer makes decisions such as choosing default or named exports and whether to export a vanilla function, an object, or a function that returns an object of other functions. All of those choices affect how the package needs to be mocked in the tests of our application code.

Below, we'll look at some tiny examples to highlight how different exports change our mock strategy.

### Default export of a vanilla function

In our first example, the library exports is a single default function. When this function is called, it executes the library's logic.

```
export default function () {
  return "real value";
}
```

To mock its implementation, we use a default import, [mock the module](https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options), and provide a factory (a function which will run when the module is invoked).

Because the module is a function, we provide a factory that returns the mock function we want to be invoked instead of the module. In this example, we provided a mock implementation so we could set the return value.

```
import example from "../defaultFunction";

const mockExpected = "mock value";
jest.mock("../defaultFunction", () => jest.fn(() => mockExpected));

it("returns the expected value", () => {
  const actual = example();
  expect(actual).toEqual(mockExpected);
});
```

### Named export of a vanilla function

In our first example, the library exports is a single named function. When this function is called, it executes the library's logic.

```
export const example = () => {
  return "real value";
};
```

To mock its implementation, we use a named import, mock the module, and provide a factory that returns an object with the named function and its mock implementation.

This is slightly different than the previous example because of the named export.

```
import { example } from "../namedFunction";

const mockExpected = "mock value";
jest.mock("../namedFunction", () => ({
  example: jest.fn(() => mockExpected),
}));

it("returns the expected value", () => {
  const actual = example();
  expect(actual).toEqual(mockExpected);
});
```

### Default export of an object

In this example, the library exports a default object that has a property for the function we want to mock.

```
export default {
  getValue: () => "real value",
};
```

To mock `getValue`, we use a default import, [spy on](https://jestjs.io/docs/en/jest-object#jestspyonobject-methodname) the imported object's `getValue` property, and then chain a mock implementation to the returned mock function.

Because `example` is an object, we can spy on its properties. If we didn't want to mock the implementation, we could leave that part off and still be able to track that the returned mock function was called.

\* Note: `jest.spyOn` invokes the function's original implementation which is useful for tracking that something expected happened without changing its behavior. For true mocking, we use `mockImplementation` to provide the mock function to overwrite the original implementation.

```
import example from "../defaultObject";

const mockExpected = "mock value";
jest.spyOn(example, "getValue").mockImplementation(jest.fn(() => mockExpected));

it("returns the expected value", () => {
  const actual = example.getValue();
  expect(actual).toEqual(mockExpected);
});
```

### Named export of an object

In this example, the library exports a named object that has a property for the function we want to mock.

```
export const example = {
  getValue: () => "real value",
};
```

Mocking `getValue` on the named export is the same as mocking it on the default export 🥳 This is one of the few cases where the export type doesn't matter because it's an object that can be spied on.

```
import { example } from "../namedObject";

const mockExpected = "mock value";
jest.spyOn(example, "getValue").mockImplementation(jest.fn(() => mockExpected));

it("returns the expected value", () => {
  const actual = example.getValue();
  expect(actual).toEqual(mockExpected);
});
```

### Default export of a function that returns an object

This example is a bit more complicated than the previous ones. Here, the library exports a default function that returns an object that has a property for the function that we want to mock. This is a common pattern to allow developers to destructure their desired function off the module function.

```
const { getValue } = example()
```

As a simple example, it looks like this.

```
export default function () {
  return {
    getValue: () => "real value",
  };
}
```

To mock `getValue`, we use a default import to [import the entire module's contents](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Import_an_entire_modules_contents) (the `* as` syntax which allows us to treat the module name as a namespace), spy on the imported module's `default` property, and then chain a mock implementation to the returned mock function.

In this case, our mock implementation is a function that returns an object with a `getValue` property. `getValue` is a mock function.

```
import * as exampleModule from "../defaultFunctionReturnObject";

const mockExpected = "mock value";
jest.spyOn(exampleModule, "default").mockImplementation(() => ({
  getValue: jest.fn(() => mockExpected),
}));

it("returns the expected value", () => {
  const { getValue } = exampleModule.default();
  const actual = getValue();
  expect(actual).toEqual(mockExpected);
});
```

### Named export of a function that returns an object

Similar to the previous example, the library exports a named function that returns an object that has a property for the function that we want to mock.

```
export function example() {
  return {
    getValue: () => "real value",
  };
}
```

Mocking this use case is very similar to the default export case above except that we need to spy on the named export rather than the default export.

To mock `getValue`, we use a default import to import the entire module's contents, spy on the imported module's `example` property (this is the named export), and then chain a mock implementation to the returned mock function.

In this case, our mock implementation is a function that returns an object with a `getValue` property, just like in our previous example.

```
import * as exampleModule from "../namedFunctionReturnObject";

const mockExpected = "mock value";
jest.spyOn(exampleModule, "example").mockImplementation(() => ({
  getValue: jest.fn(() => mockExpected),
}));

it("returns the expected value", () => {
  const { getValue } = exampleModule.example();
  const actual = getValue();
  expect(actual).toEqual(mockExpected);
});
```

## Mock behavior

We've seen how different export strategies affect how we structure our mocks. Let's look next at how to change our mocks based on the desired behavior we want within our tests.

### Browser functionality

#### For the whole test suite

If we are using a browser API throughout our application, we may want to mock it for your entire test suite. I reach for this strategy often for localStorage and sessionStorage.

For example, here's a mock implementation of `sessionStorage`.

```
export class SessionStorageMock {
  constructor() {
    this.store = {};
  }

  clear() {
    this.store = {};
  }

  getItem(key) {
    return this.store[key] || null;
  }

  setItem(key, value) {
    this.store[key] = value.toString();
  }

  removeItem(key) {
    delete this.store[key];
  }
}
```

And then in the setup file, we'll reset the global `sessionStorage` implementation to our mock implementation for the duration of the test suite.

```
const unmockedSessionStorage = global.sessionStorage;

beforeAll(() => {
  global.sessionStorage = new SessionStorageMock();
});

afterAll(() => {
  global.sessionStorage = unmockedSessionStorage;
});
```

While the tests run, any code that inserts/removes from `sessionStorage` will use our mock implementation and then we can assert on it in the test files.

```
it("sets sessionStorage isFetching to true", () => {
  const { getByText } = render(subject);
  const button = getByText(
    new RegExp(`please fetch me some cute ${animal}`, "i")
  );
  act(() => {
    fireEvent.click(button);
  });
  expect(sessionStorage.getItem("isFetching")).toEqual("true");
});
```

#### In a single file

If we're using a browser API, but want different behavior throughout our tests we may choose to mock it in the relevant test files.

This is helpful when we're using the browser fetch API and want to mock different responses in our tests. We can use a `beforeEach` block to set our `global.fetch` mock implementation.

We set `global.fetch` to a mock function and use Jest's `mockResolvedValue` ([syntactic sugar wrapping `mockImplementation`](https://jestjs.io/docs/en/mock-function-api#mockfnmockresolvedvaluevalue)) to return a mock response in the shape our code expects.

```
beforeEach(() => {
  jest.resetAllMocks();
  global.fetch = jest.fn().mockResolvedValue({
    status: 200,
    ok: true,
    json: () => Promise.resolve({ media: { poster: "hello" } }),
  });
});
```

Then we can assert that `global.fetch` was called the expected number of times.

```
 it("fetches an image on initial render", async () => {
  jest.useFakeTimers();
  render(subject);
  await waitFor(() => expect(global.fetch).toHaveBeenCalledTimes(1));
});
```

### Node modules

#### For the whole test suite

Sometimes we are using a node module throughout our code and we want to mock it for our entire test suite. In this case, we can create a [manual mock](https://jestjs.io/docs/en/manual-mocks) that Jest will automatically use during tests whenever it encounters references to that module.

In this little sample application, we're using [Voca](https://vocajs.com/) to capitalize some words in our navigation. To create a manual mock, we create a folder named `__mocks__` inside of our `src` directory and place our mock implementation there. **Note: this is counter to what the documentation says. At the time of writing, there is an [open issue documenting this](https://github.com/facebook/create-react-app/issues/7539). The fix appears to be placing your mocks inside `src` instead of adjacent to `node_modules`.**

In our mock, we use [`jest.genMockFromModule` (or `jest.createMockFromModule`)](https://jestjs.io/docs/en/jest-object#jestcreatemockfrommodulemodulename) to create an automock and then extend it with our mock implementation for the relevant function(s). By extending an automock, you limit how often you have to manually update your manual mock when the original module changes.

```
const voca = jest.genMockFromModule("voca");
voca.capitalize = (word) => `${word} capitalize mocked!`;
export default voca;
```

Then you can assert on the expected behavior of your mock within your tests.

```
it("capitalizes the current page name", () => {
  const { getByText } = render(subject);
  expect(getByText(/capitalize mocked!/i)).toBeInTheDocument();
});
```

#### In a single file

Mocking an entire node module for a single file in our test suite is not that different than what we did to mock it for the whole suite. Instead of placing our code in our setup file, we put it in the test file where we want the mocking to occur.

To mock `moment` in one test file, we can do something very similar to what we did for `pluralize`. We use a default import, mock the module, and make sure that the default return shape matches the return shape of the original implementation.

Assuming the code we want to test looks like this

```
export const toMoment = (datetime) => {
  return moment(datetime);
};
```

We would mock `moment` like this

```
import moment from "moment";

jest.mock("moment", () => ({
  __esModule: true,
  default: jest.fn(),
}));
```

Then we can assert that our mock moment function was called

```
describe("toMoment", () => {
  it("calls moment() with the correct params", () => {
    const dateParam = new Date();
    toMoment(dateParam);
    expect(moment).toHaveBeenCalledWith(dateParam);
  });
});
```

If we want to use some of the functions returned from Moment's default function, we need to update our mock to have mock implementations for those as well.

```
let mockFormat = jest.fn();
jest.mock("moment", () => ({
  __esModule: true,
  default: jest.fn(() => ({ format: mockFormat })),
}));
```

### A single function of a node module

#### For the whole test suite

Just like how we may want to mock browser functionality for our entire test suite, sometimes we may want to mock a node module for our test suite instead of in individual files.

In this case, we can mock it in our setup file so that all tests in the suite use that mock. In our sample application, we mock the [Pluralize](https://github.com/blakeembrey/pluralize) module for all of our tests.

In our `setupTests.js` file, we mock the default export.

```
jest.mock("pluralize", () => ({
  __esModule: true,
  default: jest.fn((word) => word),
}));
```

You'll note that we have `__esModule: true` here. From [Jest's documentation](https://jestjs.io/docs/en/jest-object#jestmockmodulename-factory-options), "When using the factory parameter for an ES6 module with a default export, the \_\_esModule: true property needs to be specified. This property is normally generated by Babel / TypeScript, but here it needs to be set manually."

#### In a single file

In my experience, the most common mocking use case is to mock the same behavior of one function in a node module for every test in a file. To do this, we declare mock once within the file (remembering what we know about module exports).

For example, in our sample application, we use `axios.get` to fetch cute pictures of dogs, cats, and foxes. When we're fetching pictures, we want to make sure our code is correctly calling `axios.get`. And when we're not fetching, we want to make sure we're not making unnecessary requests.

To mock `axios.get`, we use a default import, spy on the imported object's `get` property, and then chain a mock implementation to the returned mock function.

```
import axios from "axios";

jest
  .spyOn(axios, "get")
  .mockImplementation(() => Promise.resolve({ data: { file: "hello" } }));
```

And then we can assert that `axios.get` was called the expected number of times.

```
it("gets a new image on the configured interval", async () => {
  jest.useFakeTimers();
  render(subject);
  await waitFor(() => expect(axios.get).toHaveBeenCalledTimes(1));
  act(() => jest.advanceTimersByTime(refreshTime));
  await waitFor(() => expect(axios.get).toHaveBeenCalledTimes(2));
});
```

We can also use Jest's syntactic sugar functions to be even terser in our mocking code. The following two examples do the same thing as the mock implementation above.

```
jest
  .spyOn(axios, "get")
  .mockReturnValue(Promise.resolve({ data: { file: "hello" } }));
```

And even shorter

```
jest.spyOn(axios, "get").mockResolvedValue({ data: { file: "hello" } });
```

#### In a single test

Finally, sometimes we want to test different behavior within a single test file. We may have error handling or loading states that we want to mock and test that our code behaves appropriately.

In this case, we mock the function that we want with Jest's default mock, `jest.fn()`, and then we chain a mock implementation on it inside each of our test cases. I like to put the mock implementation in a `beforeEach` just inside a `describe` labeled with the case I'm testing, but you can also put it inside an individual test.

In our sample application code, we mock React Router's `useParams` hook. In our example, we're using Jest's `requireActual` to make sure we're only mocking the `useParams` function and nothing else in the module.

```
import { useParams } from "react-router-dom";

jest.mock("react-router-dom", () => ({
  ...jest.requireActual("react-router-dom"), // use actual everything else
  useParams: jest.fn(),
}));
```

And then we can set up our different use cases and assert the expected behavior.

```
describe("with a supported animal type", () => {
  beforeEach(() => {
    useParams.mockReturnValue({
      animal: mockAnimal,
    });
  });

  it("renders the correct animal component(s)", () => {
    const { getAllByText } = render(subject);
    expect(getAllByText(new RegExp(mockAnimal, "i")).length).toBeGreaterThan(
      0
    );
  });
});

describe("without a supported animal type", () => {
  beforeEach(() => {
    useParams.mockReturnValue({
      animal: "hedgehog",
    });
  });

  it("does not render an animal component", () => {
    const { getByText } = render(subject);
    expect(getByText(/oh no/i)).toBeTruthy();
  });
});
```

## Common mocking errors

I find myself running into similar errors over and over when I'm writing tests. I'm sharing fixes I've found in case it's helpful.

### `The module factory of jest.mock() is not allowed to reference any out-of-scope variables`

You'll see this error when you try to use variables that Jest thinks might be uninitialized. The easiest fix is to prefix "mock" to your variable name.

Not allowed

```
let format = jest.fn();
jest.mock("moment", () => ({
  __esModule: true,
  default: jest.fn(() => ({ format: format })),
}));
```

Allowed

```
let mockFormat = jest.fn();
jest.mock("moment", () => ({
  __esModule: true,
  default: jest.fn(() => ({ format: mockFormat })),
}));
```

### `Cannot spy the default property because it is not a function`

You'll see this error if the object does not have a function for the property you are spying. This usually means that you're not structuring your mock properly and the module is exported differently than what you're configuring. Check out the [ES6 Exports](#es6-exports) examples above to see the various ways you may need to change your spy.

### `Cannot set property of #<Object> which has only a getter`

This error comes up when trying to mock the implementation for an object which has only getters. Unfortunately, I haven't found a way around this other than completely changing my mocking strategy. I run into this most often with React Router.

Spy on default export raises this error

```
import ReactRouterDom from "react-router-dom";
jest.spyOn(ReactRouterDom, "useParams").mockImplementation(jest.fn());
```

Spy on the module contents raises "property is not a function" error

```
import * as ReactRouterDom from "react-router-dom";
jest.spyOn(ReactRouterDom, "default").mockImplementation(() => ({
  useParams: jest.fn(),
}));
```

Mocking the module, requiring actual and then overwriting the useParams implementation with a mock function works.

```
jest.mock("react-router-dom", () => ({
  ...jest.requireActual("react-router-dom"), // use actual for all non-hook parts
  useParams: jest.fn(),
}));
```

### `Warning: An update inside a test was not wrapped in act`

This is not a mocking error specifically, but one that catches me all the time.

If you're seeing this warning but you _know_ that all of your code is wrapped in `act()`, you might be asserting on promises that haven't resolved yet. React Testing Library has a handy little async utility, `waitFor`, for this exact use case.

This test raises the "not wrapped in act" warning

```
it("fetches an image on initial render", async () => {
  jest.useFakeTimers();
  render(subject);
  expect(axios.get).toHaveBeenCalledTimes(1);
});
```

Wrapping the assertion in `waitFor` resolves the warning.

```
it("fetches an image on initial render", async () => {
  jest.useFakeTimers();
  render(subject);
  await waitFor(() => expect(axios.get).toHaveBeenCalledTimes(1));
});
```