Snippets I Always Need to Look Up When Writing Tests with React Testing Library
Jamena McInteer
Posted on April 15, 2020
Whenever I start a new project and need to write React Testing Library (RTL) unit tests, I end up having to look up or copy snippets of code to set up the tests. I thought it might be useful to share some of these here in one place, both for myself and for others who might want to look up these snippets quickly. Feel free to leave a comment if any of these don't work for you or if there is a better way to do something. I will update this post as I gather more snippets over time.
Taking a snapshot
I normally don't use snapshots in my unit tests as they are more trouble than they're worth, but sometimes when debugging a unit test that is failing I want to see what the unit test is seeing, so I'll throw in a snapshot temporarily. I can never remember the exact code for taking a snapshot, and since I don't use them I can't refer to them in another part of the codebase. So here it is:
it("renders", () => {
const { asFragment } = render(<MyComponent />);
expect(asFragment()).toMatchSnapshot();
});
Mocking Axios API Calls
This is something I've had to learn how to do from several different sources as most articles will provide the basics using get
for example, and skip over how to mock post
requests. Or they will assume one call is being made instead of potentially many calls. Here's what I have figured out so far.
Setup
import axios from "axios";
...
jest.mock("axios");
const mockedAxios = axios as jest.Mocked<typeof axios>;
CancelToken
I use CancelToken to cancel axios API calls when components unmount. The above code is modified to the following to support CancelToken:
import axios from "axios";
...
jest.mock("axios");
const mockedAxios = axios as jest.Mocked<typeof axios>;
type Canceler = (message?: string) => void;
class CancelToken {
public static source() {
const cancel: Canceler = jest.fn();
const token = new CancelToken();
return {
cancel,
token
};
}
}
// @ts-ignore
mockedAxios.CancelToken = CancelToken;
Note that this works for me but does throw TypeScript errors as its not fully built out.
Get
it("renders", () => {
const response1 = {
data: [], // populate with mock data as desired
status: 200
}
const response2 = {
data: [], // populate with mock data as desired
status: 200
}
mockedAxios.get.mockImplementation(url => {
if (url === "/api/somePath") return Promise.resolve(response1);
if (url === "/api/someOtherPath") return Promise.resolve(response2);
// etc.
});
const { getByText } = render(<MyComponent />);
...
});
Post
it("renders", () => {
const response1 = {
data: [], // populate with mock data as desired
status: 200
}
const response2 = {
data: [], // populate with mock data as desired
status: 200
}
mockedAxios.post.mockImplementation((url, payload) => {
if (url === "/api/somePath" && payload.someProperty === "some value") {
return Promise.resolve(response1);
}
if (url === "/api/somePath" && payload.someProperty === "some other value") {
return Promise.resolve(response2);
}
// etc.
});
const { getByText } = render(<MyComponent />);
...
});
Render with Theme (Styled Components)
I will normally create a render-methods.tsx
file and add the different render methods I need and import them into my tests. One of these is rendering with the Styled Components theme.
import React from "react";
import { render } from "@testing-library/react";
import { ThemeProvider } from "styled-components";
import theme from "../../theme"; // or wherever your theme file is
export function renderWithTheme(component: React.ReactNode) {
return render(<ThemeProvider theme={theme}>{component}</ThemeProvider>);
}
Render with Router (Reach Router) and Mocking navigate
In my render-methods.tsx
file, I have a renderWithRouter
method that also incorporates rendering with a Styled Components theme. If you're not using Styled Components or a theme, this portion can be removed of course (see the second code snippet).
import React from "react";
import { render } from "@testing-library/react";
import { ThemeProvider } from "styled-components";
import {
createHistory,
createMemorySource,
LocationProvider
} from "@reach/router";
import theme from "../../theme"; // or wherever your theme file is
export function renderWithRouter(
component: React.ReactNode,
{ route = "/", history = createHistory(createMemorySource(route)) } = {}
) {
return {
...render(
<ThemeProvider theme={theme}>
<LocationProvider history={history}>{component}</LocationProvider>
</ThemeProvider>
),
history
};
}
Without theme:
import React from "react";
import { render } from "@testing-library/react";
import {
createHistory,
createMemorySource,
LocationProvider
} from "@reach/router";
export function renderWithRouter(
component: React.ReactNode,
{ route = "/", history = createHistory(createMemorySource(route)) } = {}
) {
return {
...render(
<LocationProvider history={history}>{component}</LocationProvider>
),
history
};
}
In my test file, I mock navigate
so it works properly and can be tested.
import React from "react";
import { fireEvent, wait } from "@testing-library/react";
import { navigate } from "@reach/router";
jest.mock("@reach/router", () => {
const RouterMocks = jest.requireActual("@reach/router");
return {
...RouterMocks,
navigate: jest.fn()
};
});
it("navigates", () => {
const { getByText } = render(<MyComponent />);
fireEvent.click(getByText("Submit"));
await wait(() => {
expect(navigate).toHaveBeenCalledTimes(1);
expect(navigate).toHaveBeenCalledWith("/pathOfNextPage");
});
});
If you need to mock parameters in the route, you can add that to the jest mock as well:
jest.mock("@reach/router", () => {
const RouterMocks = jest.requireActual("@reach/router");
return {
...RouterMocks,
navigate: jest.fn(),
useParams: () => ({
someParameter: 'some-parameter-value'
})
};
});
Dealing with Reach UI styles not found warning
If you're using Reach UI for components like Tooltip
or Dialog
, you may get a warning about styles not found. This is a work-around to get that warning to go away (this warning does not show up in the browser, just in unit tests).
const { getComputedStyle } = window;
beforeAll(() => {
window.getComputedStyle = jest.fn().mockReturnValue({
getPropertyValue: jest.fn().mockReturnValue("1")
});
});
afterAll(() => {
window.getComputedStyle = getComputedStyle;
});
Clicks with User Event
Since some components may use onMouseDown
instead of onClick
, using RTL's fireEvent
to simulate clicks may not work. I use the user-event
package instead which handles this case.
import userEvent from "@testing-library/user-event";
...
it("clicks", () => {
const { getByText } = render(<MyComponent />);
userEvent.click(getByText("Submit"));
});
Stubbing Date.now()
Sometimes you'll want to stub Date.now() to a fixed time when making time comparisons. There are a few different ways of doing this, but this is a simple method that I use in my tests.
it("stubs a date", () => {
const realDateNow = Date.now.bind(global.Date);
const dateNowStub = jest.fn(() => 1577838600);
global.Date.now = dateNowStub;
// Write the unit test as usual
global.Date.now = realDateNow; // reset the global.Date.now when you are done
Handling TypeScript error regarding property 'value'
When using TypeScript with React Testing Library, I get the following error when trying to assert on an input field's value: Property 'value' does not exist on type 'HTMLElement'
. This can be remedied by casting to HTMLInputElement
. It's not particularly pretty having this all over your tests, but it works.
For example, this would throw the TypeScript error:
expect(getByLabelText("Name").value).toBe("Lilliana");
This can be updated to the following to fix the TypeScript error:
expect((getByLabelText("Name") as HTMLInputElement).value).toBe("Lilliana");
Posted on April 15, 2020
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
April 15, 2020