ReactTDD.com

React test mocks cheatsheet

This post is a quick rundown of all the techniques you’ll need to successfully use mocks within your React unit tests.

Some general advice on test doubles

Don’t be afraid of using mocks, aka test doubles. The benefits of mocks outweigh the drawbacks. It’s actually pretty easy to use mocks well, and use them simply. You just have to stick to a few rules:

If you’re curious about why you want to be using mocks, consider buying a copy of Mastering React Test-Driven Development.

Function mocks

Creating a spy let mySpy = jest.fn()
Creating a stub
(in a beforeEach block)
let myStub = jest.fn(() => "return value")
Overriding a stub value
(in a specific test)
x.mockReturnValue(...)
Setting a stub for a
promise-based browser API
(see below for more)
x.mockResolvedValue(...)
Testing a spy (no args) expect(mySpy).toBeCalled()
Testing a spy (with args) expect(mySpy).toBeCalledWith(a, b, c)
Testing last invocation of a spy expect(mySpy).toHaveBeenLastCalledWith(d, e, f)

Spying and stubbing the Fetch API

Stubbing out a default return value in a beforeEach block

export const fetchResponseOk = (body) => ({
  ok: true,
  json: () => Promise.resolve(body),
});

...

beforeEach(() => {
  jest.spyOn(global, "fetch")
    .mockResolvedValue(fetchResponseOk("HTTP response body"))
})

Overriding a stub value in an individual test

global.fetch.mockResolvedValue(fetchResponseOk("another body"))

Overriding with a HTTP 500 response

export const fetchResponseError = (
  status = 500,
  body = {}
) => ({
  ok: false,
  status,
  json: () => Promise.resolve(body),
});

...

global.fetch.mockResolvedValue(fetchResponseError("your error"))

Component mocks

The golden rule is keep it simple! For the majority of cases, the first version is the one you’ll need.

Stubbing out a component

import { MyComponent } from "./MyComponent";
jest.mock("./MyComponent", () => ({
  MyComponent: jest.fn(() => <div id="MyComponent" />),
}));

Stubbing out a component (if component children are being tested)

import { MyComponent } from "./MyComponent";
jest.mock("./MyComponent", () => ({
  MyComponent: jest.fn(({ children }) => (
    <div id="MyComponent">{children}</div>
  )),
}));

Stubbing out a component (if you need to differentiate between multiple instances)

import { MyComponent } from "./MyComponent";
jest.mock("./MyComponent", () => ({
  MyComponent: jest.fn(({ id }) => (
    <div id={`MyComponent-${id}`} />
  )),
}));

Testing the right props were passed

expect(MyComponent).toBeCalledWith(
  {
    a: "test",
    b: "another"
  },
  expect.anything()
);

Testing the element was rendered

(Change this depending on the test framework you’re working with - for React Testing Library, see component mocks with React Testing Library.)

expect(document.querySelector("#MyComponent")).not.toBeNull();

Testing a subset of props

You can use expect.objectContaining and expect.arrayContaining to ensure you match just the specific pieces of tests that matter. This helps keep your tests independent.

expect(AppointmentFormLoader).toBeRenderedWithProps({
  original: expect.objectContaining({
    customer: "123",
  }),
});

Grabbing access to specific props

Sometimes, you’ll want to grab a specific prop that was passed to a component mock so it’s easier to work with. You can do that with the propsOf function:

export const propsOf = (mockComponent) => {
  const lastCall =
    mockComponent.mock.calls[
      mockComponent.mock.calls.length - 1
    ];
  return lastCall[0];
};

That’s used like this:

// with AppointmentFormRoute mocked out
it("navigates to / when AppointmentFormRoute is saved", () => {
  renderWithRouter(<App />);
  const onSave = propsOf(AppointmentFormRoute).onSave;
  act(() => onSave());
  expect(history.location.pathname).toEqual("/");
});

Finally, you can use the propsMatching helper to matchs props when there’s more than one component mock instance on a page. This allows you to find a single component instance based on the values of a prop set.

Say, for example, you have multiple rendered RouterButton instances:

render(<SearchButtons {...testProps} />);
expect(
  propsMatching(RouterButton, { id: "previous-page" })
).toMatchObject({ disabled: false })

And here’s the definition of propsMatching:

export const propsMatching = (mockComponent, matching) => {
  const [k, v] = Object.entries(matching)[0];
  const call = mockComponent.mock.calls.find(
    ([props]) => props[k] === v
  );
  return call?.[0];
};

— Written by Daniel Irvine on August 22, 2022.