How to Write Your First Component Test in React + TypeScript App

In the previous post, we created a unit test for a function. In this post, we're going to create a unit test for a component using @testing-library/react.

Since we're using Create React App the React Testing Library is already installed in our dependencies. If you don't have it, install the package using:

What We're Going to Test#

We will create a Greetings component with the greetings text inside and a button for sending friendly waves 😊

Let's create and review a component first:

This component takes a name and an onSendWaves function as props. The name will be rendered in the greetings text, and the callback function will be called whenever the button is pressed. The button can be hidden if the callback function is not provided.

We need to test 3 things here:

  • The name gets rendered in the p element with the correct text.

  • The button doesn't get rendered if the callback function is not provided.

  • The callback function gets called when the button is pressed.

Let's start with the first one.

Testing the Greetings Text#

For the first test, let's check if the greetings text is rendered correctly.

Let's break the test code down a bit. In describe and it we explain the assumption we want to test as we did in the previous post.

Then, we use the render method from the @testing-library/react package. This method renders the component provided in arguments and appends it to the body of the document.

“What document? We're in the console...”
The document is simulated with JSDOM and it implements basic APIs of the browser's DOM. The simulated document object can be traversed as if it were the real DOM.

Once we're rendered the component we can use the screen object to get access to the render result. It provides a set of queries for us to find the required element.

If you saw tests with RTL before, you might have seen this:

This is considered bad practice now. It is recommended to use screen because that way you no longer need to keep the destructure in the render calls up-to-date with your needs.

We use getByText query to find an element that contains the given text. Notice that React Testing Library doesn't focus on the component implementation. We don't explicitly define where to look for that text, instead, we tell what to find and RTL will try to find it for us.

Finally, we check that the required element is indeed in the document.

Query Types#

We can select elements not only by the text they contain. In fact, there are quite a few ways to query elements. Let's review a couple of types of queries:

  • ByLabelText, find an element by a given text in label or aria-label

  • ByPlaceholderText, find by input placeholder value;

  • ByAltText, find by img alt attribute;

  • ByDisplayValue, find by form element current value;

  • ByRole, find by aria role;

  • ByTestId, find by data-testid attribute.

Another cool (and a bit confusing at first) feature is the query type. Until now we only saw a getBy query. It searches for a single element and returns it if found and throws an error if didn't find.

There are also other types of queries for different types of searches:

  • getAllBy*, tries to find an array of element, if failed throws an error;

  • queryBy*, tries to find an element, returns null if failed;

  • queryAllBy*, tries to find an array of element, returns empty array if failed to find any;

  • findBy*, tries to asynchronously find a single element, returns a Promise;

  • findAllBy*, tries to asynchronously find an array of elements, returns a Promise.

The queryBy and queryAllBy are usually used to make sure that elements are not in the document. The findBy and findAllBy are used to search for elements that are not available at first render.

It is hard to pick a query at first, that's why there is a priority guideline in the docs and the cheatsheet to help us.

In short, RTL forces us to interact with the code as would our users. First of all, try to use accessibility features like role, label, value, and text. Then, semantic features like alt-text and title. If none of those work, then use data-testid attribute.

Testing the Button Render#

Now, let's test that when the onSendWaves is provided the button gets rendered:

Here, we use getByRole query because we need to find a single button element.

Testing the Button Click#

React Testing Library has the fireEvent object that can help with simulating browser events, however, it is more convenient to use the user-event library since it provides more advanced simulation of browser interactions.

Let's install it with:

Now let's test that when the button is pressed it fires the callback:

First of all, we use jest.fn method to create a mock function. This mock will help us to test how many times and with what arguments this function has been called.

On the last line, we check that the mock instance (onSendWavesMock) is called at least once with a given text. Another text will result in the failing test.

To click the button we use the userEvent object from React Testing Library. It provides most common actions like click, input changes, and so on. In our case, we need to test the click action, so we use the click method and pass the element that should be pressed.

What's Next#

In the next post, we will test react hooks with @testing-library/react-hook.