UI Testing with React Testing Library and Jest
By: Meriya Susan Thomas
Tags: Software Engineering, React, Unit Testing, Jest, React Testing Library

UI Testing with React Testing Library and Jest

Photo by Alvaro Reyes on Unsplash

Introduction

In my team, we used to write a lot of UI unit tests in our Backbone application. Unfortunately, they were not very resilient to code changes or new use cases.

Recently, we have been creating React components. So we had an opportunity to re-think unit testing. Soon we realized there are two kinds of unit testing we can do:

Test the internal working of React components with Jest & Enzyme Test the component from an end-user perspective with Jest & React Testing Library

We decided to go with the second type.

These are a few UI unit testing guidelines that are inspired by the React Testing Library philosophy:

https://testing-library.com/docs/guiding-principles

Avoid testing implementation details

Tests shouldn’t break for every small code change (refactoring) that doesn’t change the UI

When tests focus on implementation details, they are hard to maintain

Tests are an alternate way of documenting how the code renders for the user

Testing as an end user and as a developer

End user interacts with the component

Developer user renders the component

Avoid a test user in unit tests who tests any implementation details

Tests should reflect how an end-user might see and interact with the component as closely as possible

There is no need to have 100% code coverage

We should have good coverage numbers but it’s almost useless and will lead to brittle/slow testing if we go for 100% code coverage

Quick recap of Jest

Jest is a testing framework for testing JavaScript. It’s very well documented and requires very little configuration to get started.

We will use Jest as the test runner so let’s go over a few basic Jest concepts very briefly. This is an example of a test:

Example of a simple Jest test

In the above test:

describe function is a test suite

function is a test suite test function is a test case

function is a test case expect function is an assertion

We are testing a function called hello() , which is in another file, so we import it into hello.spec.js and call it.

React Testing Library

This is a really nice lightweight testing library for testing React components by querying and interacting with DOM nodes. It has very easy utility methods to simulate an end-user. It also works very well together with Jest.

Let’s say we have a SearchForm React component which may consist of input fields and a search button. To test it, we could write:

Using Render, Screen API from React Testing Library

Here in the above test, we have two methods from @testing-library/react :

render is a utility method provided by React Testing Library. It renders a react component into a container and appends to document.body . screen is an object which is exported by RTL (React Testing Library) and has access to all the query methods that check HTML elements visible in document.body .

RTL: Query methods

byRole

This will query elements with a certain role that they have been assigned. Here is a good page for reference to HTML elements and their roles.

For example <a href> will always have a “link” role, while <button> will have a “button” role.

byText

This will look for elements having a particular text content.

byLabelText

This will try to match a label present in the HTML and return an element that is associated with that label.

byPlaceholderText

This will look for elements with a placeholder attribute and match the text.

There are a few more query methods documented here.

RTL: Search variants

getBy

This method returns either an element or an error if an element cannot be found. This way, the test can fail fast and early, but it is difficult to test for elements that are not supposed to be there.

E.g.: getByRole

queryBy

This is the same as getBy but we can also assert for elements that are not present without getting an error.

E.g.: queryByRole , queryByLabelText

findBy

This is to assert for any elements that aren’t there yet but will be eventually. This returns a promise, which gets resolved when an element is found.

E.g.: findByText

Multiple elements

All the below variants return an array of elements.

getAllBy

queryAllBy

findAllBy

RTL: Assertions

Usually, assertions come from Jest. React Testing Library extends the Jest assertions to include more focused DOM assertions.

These are some of the assertions that we have used so far:

toBeVisible()

toBeEnabled()

toBeDisabled()

toHaveTextContent()

Check out the rest of the assertions in the jest-dom package.

RTL: User interactions

React Testing Library also provides a user event library: @testing-library/user-event .

The userEvent API fires events in a way that mimics the actual browser behavior. If we were testing the click of a search button in our search form example above, it would look like this:

Using a user event for user interaction testing

Testing asynchronous behavior with React Testing Library

React testing Library provides an async utility method called waitFor , which we are in this test. This is particularly useful for a unit test that mocks the API and waits for any mock promises to resolve.

//Wait till the mock API gives back a successful result

await waitFor(() => screen.getByText("Search Results"),

{ timeout: 3000 });

Mocking

How to mock modules/components

With Jest, we can create a mock for the entire module/service/component by creating a mock file with the same name and placing it inside __mocks__ folder.

jest.mock() takes the path to the module that we are mocking and when it encounters the module in the test, it swaps out the real module with the mocked module.

How to mock functions

Jest also allows for us to mock functions using jest.fn() .

Here is an example of a test with mocks and async behavior testing:

An example test with mocking and testing async behavior

Wrapping up

I really liked React Testing Library because it forces the developer to ignore the implementation details and really test user behavior. Having written our unit tests in React Testing Library, we are confident that if any of our tests were to fail after a code change, it would mean that an existing end-user experience has changed. This will make us more conscious of the changes we make in either the UI code or in the test to support the new changes because now our tests closely resemble real end user scenarios.