Testing with Jest and React Testing Library
Tags: react, reacttestinglibrary, jest
Published: 2022-09-18 08:20:18

---
title: Testing with Jest and React Testing Library
published: true
description: An introduction to testing React applications with Jest and React Testing Library
tags: react, reacttestinglibrary, jest, 
cover_image: https://www.diogorodrigues.dev/static/e3de711c592b56f4e44717f88532215d/cb943/automating-ui-tests-with-jest-and-react-testing-library.jpg
---

[Jest](https://jestjs.io/) is a library developed by Meta for testing JavaScript applications. [React Testing Library](https://testing-library.com/), on the other hand, is a library developed by Kent C Dodds and other open source contributors, which wraps around the React library and provides a black box solution specifically for unit testing [React](https://reactjs.org/) applications.

At Visa, I was writing unit tests for a [Next.js](https://nextjs.org/) project using components designed with [Chakra UI](https://chakra-ui.com/). That's where React Testing Library came in handy. Unlike other solutions like [Enzyme](https://enzymejs.github.io/enzyme/), I did not have to worry about the application snapshot but could instead focus on each UI element, its expected behaviour and the data it would render upon user interactions.

## Jest

In Jest, you usually split your tests into groups using the `describe` block. You can write a descriptive message as the title of the block to indicate what kind of tests you are including in this block. Similarly, each individual test case is written using the `it` or `test` wrapper, with a message to describe the individual test case.

{% embed https://gist.github.com/aaditkamat/0b580a4ebcc9a7abca2badb788d6d5dc/5cad4337a0d7a66ca5526297ca974808c3193134 %}

The nice thing about Jest - which can also be a pain point when you actually get down to writing the tests - is that they execute in parallel rather than sequentially. You can force sequential execution by using the `--runInBand` parameter and this comes in handy sometimes while debugging issues related to timing. However, test suites are generally executed in parallel so that they run faster.

```
jest example.test.js --runInBand
```

With Jest, you write tests using the `expect` clauses, where you specify the matcher that you are expecting and compare it to the received output, to verify that the expected behaviour is satisfied.

{% embed https://gist.github.com/aaditkamat/0b580a4ebcc9a7abca2badb788d6d5dc/6a1077345d66fcbc357a014e0df6786eebcea3fb %}

You can specify the chunks of code you wish to execute before and after each test case using the `beforeEach` and `afterEach` clauses respectively. If you want to include some code before all the test cases are executed or after all the test cases are executed, you can use the `beforeAll` and `afterAll` clauses. These functions are very useful for consolidating all the common data setup as well as doing some cleanup.

{% embed https://gist.github.com/aaditkamat/0b580a4ebcc9a7abca2badb788d6d5dc %}

[Mocking](https://jestjs.io/docs/mock-function-api) is a useful feature in Jest that allows you to test the expected behaviour when an API call is made without actually calling the respective APIs. This is necessary for testing because calling the actual APIs can be costly, since these calls are subject to constraints like network bandwidth. These can also be used for simulating calls to third party dependencies, which are not supposed to be tested because they have been developed by others.

## React Testing Library

These are the abstractions (functions) from the React Testing Library that I have used most for writing unit tests:

1. `screen`: The `screen` API allows you to access the HTML DOM, and print out either the entire DOM or a portion of it using the `debug` method. You can also retrieve element(s) by using the `findBy*`, `queryBy*` and `getBy*` functions. `findBy*` is an asynchronous function that allows you to fetch an HTML Element or an array of HTML Elements. `queryBy*`, on the other hand, allows you to fetch either as well as return null if they are not found. `getBy*` throws an error if the element(s) in question is not found. `findAllBy*`, `queryAllBy*` and `getAllBy*` are variants of the respective methods that let users retrieve an array of matching elements instead of a single element.

2. `waitFor`: `waitFor` allows you to execute a block of code and wait for it to finish before you move on to the other blocks of code in the test case.

3. `render`: `render` is used to render a React Component or Fragment as you would in a React application. This is basically a re-export of the original render function from the React library.

4.  `fireEvent`: `fireEvent` allows you to handle trigger events on the different elements like `input` or `select`. An alternative is the `userEvent` library for simulating the user interactions.

## Example of a unit test case

Let's say we build a login form using Chakra UI. We want to do very basic validations on the form data and check the logic flow once the `submit` button is called.

{%embed https://gist.github.com/aaditkamat/9639c0bbcd56198b9d931ece84c2182c %}

You only need to specify the respective test ids for the components while designing the front end. You can then match the values filled in with the expected values after carrying out the interactions. Here, the mock for the `console` object comes in handy to determine whether the statement is printed upon successful login or not.

## Pain points:

While writing unit tests seems straightforward in theory, it requires a good amount of trial and error in practice. 

These are some of the issues that I faced along the way:

### 1. Time out issues
If the tests involve a large set of UI interactions like filling in multiple fields in a form or handling multiple input events, they are likely to time out before the whole test case executes, or even in the waitFor block. A work around is using [`fakeTimers`](https://jestjs.io/docs/jest-object#jestusefaketimersfaketimersconfig) in jest and then [`runAllTimers`](https://jestjs.io/docs/jest-object#jestrunalltimers) once the component is rendered so that the test timer waits for the completion of the execution. Another way, is to use [`setTimeout`](https://jestjs.io/docs/jest-object#jestsettimeouttimeout) and specify the timeout you are expecting. 

The main problem is detecting the line(s) of test code responsible for the delay in test execution. For this, you have to usually resort to some trial and error with reordering the test cases in the suite or even the way you break the test cases into suites. You might also have to log the execution of the source code at several points to see whether the data obtained from a mock is the same one that the test was expecting at that point in time. The problem is compounded due to the asynchronous nature of execution of React code in hooks.

### 2. Test cases pass individually but fail when run in a suite

Honestly, this is a very frustrating problem to solve. The first easy step to troubleshoot this issue is to have all the shared data setup for the test cases in the `beforeEach` and `afterEach` clauses. However, it could also be that some of the tests have taken longer to execute than others and therefore, only part of the source code gets tested in a failing case. Resolving this issue involves significant trial and error similar to the timeout issue.

### 3. Element not found

Sometimes the element(s) you are looking for simply cannot be found. You would have to print out the whole DOM using `debug` and inspect why this is not found. It's an easy fix if the matcher specified in the `expect` clause is wrong, but it becomes quite troublesome if the element is not displayed because the state of the application has not been updated.  This might require you to inspect the application flow more thoroughly. It's good if the test has helped you catch the bugs in code execution, since that is the main purpose behind software testing. However, it is also possible that your code is executing correctly but there is some other issue that results in this error, like a timeout issue.