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:
- Use mocks for spies and stubs only. Spies allow you to test the parameters and props values. Stubs override return values from mocked functions, or the rendered JSX from component mocks.
- Return hardcoded values from your stubs. Once you introduce an
if
or any kind of control logic, that’s no longer a stub but a fake. Fakes are an option of last resort. Instead, you should use multiple different stubs for your different test cases. - You can mock React components. Use them as spies to check the value of props passed. If you’re being diligent you can also assert that the component was rendered into the document in the way you expect.
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.