What I wish I knew when I started using react testing library

I started testing with the react-test-library and ran into a lot of pain. Most of my pain came from the fact that I did not take the time to learn the library first. Thus I had no understanding of how it worked. I tried to make it work from existing examples and from a lot of trial and error but without the understanding, it caused a lot of frustration. I stumbled several times and scraped my knees and elbows along the way.

I then went through portions of a course on Plural sight and got somewhat of a better understanding. But when I went back to try them out on my code I still had trouble making them work. So I had to dig a little more.

Hopefully this post will get you a better understanding and you won’t stumble as much as I did.

First things first, the library is actually called Testing Library. I know! I know! very generic name… but that’s what it is. But wait you may think “I thought it was called ‘react testing libary’?” Well react testing library is a just a react specific API from Testing Library to React code. In the same manner the library provides different APIs such for libraries like reason, native, Vue, Angular and many more.

With that in mind, you must know that there is a Core API portion. The Core API portion has all the core functionality of the API. We can get access to that in our React app by including the React Testing Library.

The Core API

The Core API library is composed of two main concepts; Queries and User Actions. Queries are the API pieces that allow you to query your react DOM. User Actions allow you to fire events to force the component to change its state such as button clicks and changing text on input controls or even doing things like changing focus (i.e. forcing blur events).

Queries

Queries on an element are getBy…,queryBy…,findBy…, notice the …. That is because those queries are combined with ByRole, ByLabelText, ByText, ByTestId and so on. For example you could have getByText(‘Click Me’) and it would query the element or component for an element that has the text ‘Click Me’ (notice I said element because it will not restrict it to a button). You can also use regular expressions with getByText, like getByText(/click me/i) which will ignore casing. You can also getByRole(“button”) and that would get an element that has the attribute role=”button”.

The queries can return singular values as in the example above or return multiple values by using the getAll… instead of get and findAll… instead of find, etc. For example getAllByText(‘foo’) would return an array of elements with the text ‘foo’.

The getByText gets the element that has the text in it. For example

getByText('foo');

would get me this element

<div ...> foo  </div>

Understanding the different types of queries

Another concept to mention regarding the different query types is in how they behave. All of them will throw an error if the results are greater than one. The queryBy is the only one that will not throw an error if the result is less than one. And findBy is the only one that is asynchronous. So… findBy must be combined with await inside of an async function. GetAll and Find can return 1 or more elements. Still have not researched what getAll, findAll and queryAll return when no item is found but be aware when you use it. It may throw an error or return an empty array.

What this means is that you have to know what your going to do with the results of the query. If your intent is get the element because it should exist getBy is fine. On the other hand, if your intent is to make sure the element does not exist you probably need to use queryBy. Since queryBy returns null, you can then check for null for your test. We haven’t discussed how to test yet but we’ll get there.

There is one alternate method to all the api queries and that is the querySelector which is practically a like a regular DOM selector for anything you can think. It is extremely generic an its use should be justified.

The React Testing Library

To use the react testing you should refer to the official API pages but as of this writing I’ll do my best to highlight it’s usage. First you must install it. Second you must import the functions that you would like to use. Some common ones are render, fireEvent, waitFor, and screen.

import {render, fireEvent, 
               waitFor, screen} from '@testing-library/react'
import '@testing-library/jest-dom'

Like most unit testing you can take advantage of the beforeAll, beforeEach, afterEach and afterAll functions for any setup and tear down of your tests.

beforeAll( () => ...dosomething);
beforeEach( ()=> ...);
afterEach( () =>...);
afterAll ( () =>...);

Then you can begin writing tests in the format of

test('renders alert', async () =>{
  
  const {getByText, getByRole} = render(<MyComponent val1={"..."} />);

  expect(getByText(/welcome/i)).toBeInDocument();
  fireEvent.click(getByRole('button'));  
  
  //notice the await and async up top
  await waitFor(() => getByRole('alert'); 

  expect(getByRole('alert')).toHaveTextContent('Oops, somethig failed'); 
});

Note how the getBytext and getByRole are not imported but rather they are extracted from the object that the render returns. There is also another way to do this using screen. Let’s take a look.

test('renders my component',   async () =>{
  
  render(<MyComponent val1={"..."} />);

  expect(screen.getByText(/welcome/i)).toBeInDocument();
  fireEvent.click(screen.getByRole('button'));  
  
  //notice the await and async up top
  await waitFor(() => screen.getByRole('alert'); 
  expect(screen.getByRole('alert'))
     .toHaveTextContent('Oops, somethig failed'); 
});

In this second example notice that I did not extract the get getByText and getbyRole functions out of the renders return. Instead they automatically get set in the screen object that was imported at the top of the file.

One more piece to this and can be very significant is the debug function. Whenever you render a component, you can call the debug function

screen.debug()

or

const {containter} = render(<MyComponent/>);
container.debug();

The debug will spit out the information html to the console screen. this can be very helpful when you need to see what is in your DOM.

There are some advanced features and much more that you can get from the official site but hopefully this will give you a better head start then going directly to the official site.

Well to summarize these are the things that I wish I knew before I got started. There is definitely a lot of ground that I did not cover. You can find it as needed from the APIs documentation. I didn’t even touch all the different types of assertions that you can make but you can find those in the documentation.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out /  Change )

Facebook photo

You are commenting using your Facebook account. Log Out /  Change )

Connecting to %s

%d bloggers like this: