Testing your React Components — Step by Step

How to test React.js components with Jest and Testing Library. Test events, async behavior, mock API calls, plus more.

Fernando Doglio
Bits and Pieces
Published in
9 min readSep 5, 2022

--

Photo by Sigmund on Unsplash

Testing is a crucial step within the software development lifecycle. We sometimes “forget” about it. And other times we simply assume it’s a back-end task.

When in reality, testing should happen in every aspect of our code, whether it’s the back-end or the presentational front-end code, by adding unit tests into it, we’re adding a safety net around it. That net is the only thing letting us know if we’re introducing breaking changes without even noticing.

So let’s take a quick look at how you’d go about testing your React components.

The tools we’ll use

As you may or may not know, there are TONS of tools to use, but I’ll stick to what is provided by default, which means: I’ll be using JEST + Testing Library.

That’s a very powerful combo, since the first provides you with all the testing framework needed to write your actual unit tests and the latter will provide you with React-aware methods to render your components, query them by different aspects of them (like their roles, their text, their labels and many others) and you’ll be able to fire events, like clicking on a button, without having to run the test on the browser.

Setting up JEST + Testing Library

The best part about this combo is that you probably already have them installed and haven’t realized.

Both libraries are the recommended testing combo by the React team, which means tools like create-react-app already install them out-of-the-box.

And if you’re using Next.js for example, here is a sample project showing you how to set up both these libraries with the framework.

For this tutorial, I’m going to be using a project created with create-react-app , meaning that running the tests is just as easy as using the npm test command and creating new ones, is as simple as creating a file with its name ending in test.js . So if you have a component called Counter.js , a good practice to follow, is to create a file called Counter.test.js next to it.

Jest will know to look for these files all across your project, so you don’t really have to worry about it.

Testing your first component

For our first test, let’s keep it simple. I’ll create an easy Greeter component that says “hi” to you in two different ways, depending on whether you’re passing the name prop or not:

As you can see we have two potential outputs from this component, now let’s create and review the test for them:

There are a few interesting things to see here:

  1. I’m importing the render and screen elements from the Testing Library. The render function will render our component into a fake DOM which we’ll be able to query later. And the screen object will be that querying interface, notice how I’m able to get the rendered element by using the getByText method.
  2. I’m calling the test function twice, even though I never really imported it from anywhere. The same goes for the expect function. These are Jest helpers that Jest will add automatically, so I (and you) don’t really have to think about it.
  3. The way I’m testing this particular component, is by rendering either with or without the prop, and then trying to capture the rendered HTML element by looking for the expected text inside it. Then I set the expectation that the captured element should be inside the document (meaning, it should be rendered). That might be an overkill here since if the rendering didn’t go as planned, the pElement variable would be null and I could just be expecting it to not be null. So I could’ve written something like expect(pElement).not.toBeNull() and it would’ve worked just as good.

Now with this example out of the way, let’s start testing something more interesting, like a component with interactions.

Adding interactivity: firing events

The previous test was nice and simple, but we’re not really dealing with that kind of component often in our code. Are we?

Usually components have some kind of interactivity that we need to test, so let’s create a component that has a few buttons. Shall we?

I present to you, the Counter component:

This is the classic component that lets you increase and decrease a counter and shows that number on the screen. It has an added async functionality that I’ll tackle in the next section, so ignore it for now.

I have two buttons I care about right now: + and -

Every time I click on + the value state variable will be updated, and the same happens whenever I click on the - button. So let me write some code to test that:

I’m again importing the render and screen elements from the Testing Library, but this time I’m also importing the fireEvent object. This object is going to let me interact with the fake DOM I mentioned before.

If you pay attention, both tests are pretty much the same:

  1. I render the element
  2. I click on the relevant button
  3. I get the output and check if the resulting HTML has the correct value inside.

The interesting bits about those steps are:

  1. I’m getting the buttons using the text inside them. I’m not using an ID or a class, this is because a) I know my button’s text is unique, and b) the Testing Library doesn’t provide any methods to query the DOM through class names or IDs, because those can vary without affecting the actual HTML, so why would I tie my tests to those parameters? I shouldn’t, and neither should you, which is why they don’t even let you try it.
  2. I am, however, getting the resulting element using the getByTestId method. The “test id” is a special data attribute that the library will look for. It works as testing metadata, or put another way, it’s data you can use to identify your elements only within your tests, so it won’t affect your business logic and it won’t bind your tests to something that might change in the future. Notice in the component’s code I have a data-testid property on the element rendering the value variable.
  3. Finally, notice I’m accessing the outerHTML property of the captured object inside the expect call. This is because the returned value from the query (the call to getByTestId ) is not a string, in fact, it’s an HTMLElement object, so I can’t really use the string matcher method I’m using.

Let’s now go into the final step of this introductory tutorial: let’s check the behavior of a component with asynchronous behavior.

Checking for async behavior

Async behavior can mean anything really, and that’s the point of this section. You could be getting data from a 3rd party API, querying a GraphQL DB, or like me in this example, you could simply have a timeout set somewhere.

The point is that whenever you trigger an action, the result is not shown immediately. When that happens, you can’t test like I’ve been doing so far, because the test will fail, even when the end result looks correct to you.

The reason for that is that whenever you use async functions, their callbacks will be added to the execution queue, and won’t be run immediately.

Let’s see an example.

Go back to the Counter component’s code, and notice the last button: the “Show it” button. Whenever I click on it, the onClick handler will call setTimeout with a 500ms delay. After that delay, the code will update a state variable and a new p element will appear on the screen.

If my test was like this, what do you think would happen?

The expect function would be called right after the click, but the timeout would not have ended yet, so the test would fail.

Now, I know what you might be thinking: “well what if I set my timeout to be shorter?”.

Sadly, that won’t change anything, you can change the timeout of the setTimeout call to 0 for all I care, and the result will be the same, because the callback will be added to the queue instead of being called immediately.

So how do we solve this? With the waitFor helper!

We can import this helper from the Testing Library and use it to set our async expectations, like this:

It’s an easy fix, but now the test knows to wait for a while before calling it a “fail”.

How much is “a while”? By default, the waitFor function will wait 1 second (which was enough for me since I was setting a 500ms delay). However, if you have longer timeouts on your tests, you can overwrite the default value like this: waitFor( () => {...}, {timeout: <your number of milliseconds here>})

In other words, there is a second parameter to configure the behavior of the function.

Also, if you’re having bigger delays in your tests, you should double-check if they’re valid. Meaning: your tests should only be testing your components, not the external resources they deal with. If you’re testing a component that sends a request to an external API, that request should be mocked to remove the dependency between your test and the status of that API (it might be down and it would make your tests fail, making you think there is something wrong with your component).

As a matter of fact, let’s quickly look at a component that makes a fetch request and how to mock it.

Mocking external API calls inside your component’s tests

The following component makes a fetch request whenever it is rendered and displays a random activity on the screen.

If the API call fails, it displays an error message instead. Simple, yet when testing it, we don’t want to bind the result of the test to the status of the API, because we want to test our logic, not the integration.

So a test for a component like this, would require us to install the jest-fetch-mock package. By installing it, we can mock the fetch function inside our tests.

And so, we can decide when the API calls succeed and when they fail, like this:

I’ve added a beforeEach callback, which gets executed before each test to clean up the mocks created on the previous test. That’s to make sure we don’t “bleed” mocks between tests.

On the first test, I’m faking a valid response, and then I’m checking if the rendered value is correct. Notice I’m using the act function, which is a wrapper we need to use whenever our actions trigger an internal state change. I didn’t use it before with the fireEvent object, because those helper functions already use the act function internally. But here I’m updating the state when the component is rendered, so I had to manually call it.

On the second test, I’m mocking a rejection. This is the beauty of mocks, I can control exactly what happens with the request, so my tests don’t really depend on the API and I can perfectly test my logic for when the API is down for any reason without having to ask the owners of the API to take it down for my test!

That concludes this example-based testing tutorial! Did you learn anything new? Are you using other libraries to test your React component? Share them in the comments so others can learn about them!

Go composable: Build apps faster like Lego

Bit is an open-source tool for building apps in a modular and collaborative way. Go composable to ship faster, more consistently, and easily scale.

Learn more

Build apps, pages, user-experiences and UIs as standalone components. Use them to compose new apps and experiences faster. Bring any framework and tool into your workflow. Share, reuse, and collaborate to build together.

Help your team with:

Micro-Frontends

Design Systems

Code-Sharing and reuse

Monorepos

--

--

I write about technology, freelancing and more. Check out my FREE newsletter if you’re into Software Development: https://fernandodoglio.substack.com/