My adventures with React Testing Library

My adventures with React Testing Library

Writing better test cases and building more confidence

So what is React Testing Library?

React testing library is a library that believes in the philosophy of testing UI elements in the way, your user would interact with them and not how they would appear in your code. This approach helps us eliminate the consequences of writing synthetic tests, which are fragile and may not point out bugs in your code. Consider this example, imagine a button component being tested on whether it would show up or not, even though our snapshot tests may pass if the component is getting rendered, they may not be able to point out at something that may be hiding the appearance of the button on the screen, a better way to test this could be looking at it by it's role or by it's text content, the actual elements that your user interacts with - React testing library helps exactly here.

The more your tests resemble the way your software is used, the more confidence they can give you.

As the guiding principles say, tests related to rendering elements should deal with DOM nodes and not component instances.

Let's write some tests

Consider this counter component -

//ClickingCountButton.js
import React, { useState } from 'react';

const ClickingCountButton = () => {
  const [clickCount, setClickCount] = useState(0);

  return (
    <>
      <h1>{clickCount}</h1>
      <button onClick={() => setClickCount(clickCount + 1)}>Up button</button>
    </>
  );
}

Some observations -

  • The button here has a role of button :)
  • The text content of the button is Up button
  • The text content of the h1 element is 0 initially as it is initialised to 0.

Using these observations, we can start testing our component.

it('should render a button with text content - Up button', () => {
      render(<ClickingCountButton />);
      jestExpect(screen.getByTextContent("Up button")).toBeTruthy();
});

Let's break this down -

render

This renders the component into a container and appends it to the document.body, by default it creates a div and appends the rendered component inside it to the document.

If we wish to render inside our custom container, instead of a div, we can customise the container as follows -

const table = document.createElement('table')

const {container} = render(<TableComponent />, {
  container: document.body.appendChild(table),
})

screen

React testing library comes with a screen object containing pre-bound queries, these queries can then be run on the rendered component in the container and tested for conditions, as seen in the above example.

Using queries

We use queries to query for the elements on the screen, we can query elements by their role, text content, placeholder, label text, display value or also by accessibility considerations like alt text or title which are not normally visible to users.

In cases when these parameters don't make sense, React testing library provides the option to query for elements using the data-testid attribute. Since these are not related to the user's experience, it is mostly used as a backdoor entry and not recommended if other options are available.

These queries are further classified based on whether they throw a descriptive error - getBy..., return null when no matching element is found - queryBy... or return a promise which resolves if element is found or rejected if not found - findBy...

To query for more than one element, we simply use these variants of the above queries getAllBy..., queryAllBy... , findAllBy...

So, we could use queries like getByName, getByLabelText, getByTextContent findByPlaceholderText and other combinations.

So, if we wish to query the button in the component above by it's role we might write a test like -

it("should render an element with role button", () => {
      //render logic
      render(<ClickingCountButton />)

      //the assertion
      jestExpect(screen.getByRole('button')).toBeTruthy();
})

So, with all this, now start playing around with your test files :)