React Testing Library: Tips and Tricks

sheelah_b

Sheelah Brennan

Posted on August 20, 2021

React Testing Library: Tips and Tricks

Update (9/6/22): This article has been updated to stay in line with current React Testing Library best practices.

Testing front-end web applications has gotten a lot easier! But that doesn't mean it doesn't involve some ramp-up on tooling. In this article I'll provide some tips and tricks for getting started and ramping up quickly with React Testing Library, the most popular tool for React component testing today.

Getting Started

The docs are great, so if you're new to the library, you should definitely take a quick look there.

Deciding on Queries to Use

In order to write tests, you'll use the library to render your component and then write one or more queries to find certain DOM elements on the page. The question you'll have is which query to use! There are a lot of them. The best practice here is to query for actual visible elements in the DOM, such as querying for buttons with certain text, form inputs, images with certain alt text, etc.

Here's an example of querying for a button with "Read More" text:

const button = screen.getByRole("button", { name: /read more/i })
Enter fullscreen mode Exit fullscreen mode

These queries can be used to look for certain text elements too! Here's an example of querying for a h1 heading:

const button = screen.getByRole("heading", { level: "h1" })
Enter fullscreen mode Exit fullscreen mode

Role-based Queries

findByRole and getByRole queries are the ones that you will likely be reaching for the most, and any element listed in this roles list can be queried for. The bonus of using these roles-based queries is that you're helping to ensure that your components are accessible and available for assistive technology like screen readers!

For the full list of role-based queries available, see the React testing library docs.

findBy* vs getBy* Queries

So what's the difference between findBy* and getBy* queries? For any test that involves asynchronous behavior, like checking that a message is rendered after a user clicks on a submit button, findBy* queries are the ones to reach for. For tests that don't involve asynchronous behavior, like checking that a heading with certain text is shown when component is rendered, getBy* queries work great.

Note that with findBy* queries, you'll use await to wait for the element you're querying for to be found:

const confirmation = await screen.findByRole("heading", { level: "h3" })
Enter fullscreen mode Exit fullscreen mode

You can think of findBy* queries as a shortcut for running a getBy* query followed by an await waitFor() call. The following two approaches would accomplish the same thing but you'll see that using findBy* is more concise:

// Using findByRole() to wait for a toast to appear
const toast = await screen.findByRole("heading", { level: "h3", name: 'Confirmed!' })
expect(toast).toBeDefined() // This is optional since the line above will fail if the toast isn't found

// Using getByRole() to wait for a toast to appear
await waitFor(() => {
  const toast = screen.getByRole("heading", { level: "h3", name: 'Confirmed!' })
  expect(toast).toBeDefined()
}
Enter fullscreen mode Exit fullscreen mode

getBy* vs queryBy* Queries

You'll also notice that there are both getBy* queries and queryBy* queries. The main difference is that getBy* and findBy* queries return the actual DOM element matched and throw an error if the element is not found. queryBy* queries are similar in that they also return the actual DOM node matched, but they return null if no match was found. How do you know which to use? Reach for getBy* and findBy* queries unless you want to test for an element that may not be present. In that case you'll want to use queryBy* queries. An example of a good use case for queryBy* queries is when you want to test that an element is not present.

Querying for Single and Multiple Items

With React Testing Library, you'll notice that there are both getAllBy* and getBy* queries. When you're just searching for a single element, you'll want to use a getBy* or findBy* query. For cases where you want to query for multiple items, such as an unordered list of elements, you'll want to use a getAllBy* or findAllBy* query.

Debugging Errors or Missing Elements

Sometimes your tests will fail unexpectedly and you'll wonder what is being rendered. Don't worry -- there's a utility for that! Use screen.debug() inside your test and you'll then get a full printout of what was rendered. If your component renders a lot of of content and screen.debug() output is getting cut off, specify a longer max output length by doing something like screen.debug(undefined, 50000) to print 50000 lines. Alternatively, you can print the output for a given DOM element that you queried for with screen.debug(myElement, 500000).

Testing Hidden Elements

Sometimes you'll want to test a component that is hidden. For example, an element might have aria-hidden=true on it if it has surrounding label text. In this case, if you query for the element, you'll find that you don't get any matching element found.

The fix is to include { hidden: true } in your query. Then the library will also include hidden elements in query results.

Example:

const buttons = screen.getByRole("button", { hidden: true })
Enter fullscreen mode Exit fullscreen mode

See the documentation on this for more information.

Testing Click Behaviors

For tests that depend on a user interaction, like a button click, to happen, you'll want to simulate that interaction in your test first. To do this, you can use the userEvent library:

const user = userEvent.setup()

const submitButton = screen.getByRole("button", { name: /sign up/i })
await user.click(submitButton)

const toast = await screen.findByRole("heading", {
  level: "h3",
  name: "Confirmed",
})
expect(toast).toBeDefined()
Enter fullscreen mode Exit fullscreen mode

There is also an older utility called fireEvent that you can use, but using userEvent is preferred since it more closely simulates an actual user interaction.

More Test Assertion Options

To have access to a wider array of Jest assertion options to use in your tests, I highly recommend installing the jest-dom library. It works great with React testing library and lets you write some useful assertions without extra legwork. For example, you can test that a link element has a certain href attribute like:

const link = screen.getByRole("link", { name: "Get Started" })
expect(link).toHaveAttribute("href", "https://example.com")
Enter fullscreen mode Exit fullscreen mode

Query Helpers and Tools

To help with finding an appropriate query, there is a useful Chrome extension that you can try that's called Testing Playground.

To help ensure that you're following all best practices for the library, there's also an ESLint plugin that's worth installing in your project.

That's all! I'd love to hear what your favorite React Testing Library tip is. Feel free to share in the comments or find me on Twitter.

Featured image by Jörg Angeli via Unsplash

💖 💪 🙅 🚩
sheelah_b
Sheelah Brennan

Posted on August 20, 2021

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related