How to Write Functional Tests in React (Part 1)

Photo by Annette Keys on Unsplash

This is the first in a series of articles that will help you build a solid foundation for testing React components.

At the beginning of the year, I set a goal to improve my code quality by focusing more on testing. I tried a handful of libraries and methods like Enzyme, react-test-render, and snapshot testing. I eventually landed on react-testing-library.

What is react-testing-library?

react-testing-library (RTL) is a testing framework created by Kent C. Dodds with the guiding principle of

The more your tests resemble the way your software is used, the more confidence they can give you.

How is RTL different than other frameworks like Enzyme?

RTL structures your test in a way that doesn’t include the implementation details of your components. As such, when your components are refactored in the future (to change implementation but not functionality), your tests do not break and your time isn’t spent fixing it.

Unlike Enzyme, RTL will test DOM nodes instead of component instances if said component is related to rendering. If you’d like an in-depth analysis, Kent C. Dodds has a comprehensive article about his dislikes with shallow rendering in Enzyme.

The TL;DR is that RTL does not include a lot of the utilities that Enzyme has (such as and ) because they

are things which users of your component cannot do, so your tests shouldn’t do them either.

How can I use RTL effectively?

We’ll create some basic tests for asserting form inputs. Here’s what our component will look like:

Our Component

MyInput.js (but it’s actually a form 🤫)

  • On the initial creation of this functional component, we assign and with the hook
  • When the user types in the input we fire the function and set and to the user’s input
  • The is shown with when the input is no longer pristine (user types something) nor valid (less than 1)
  • The is disabled when the input is not valid (less than 1)
  • is an attribute used by RTL to find elements

Note: I call the component MyInput and I realize it isn’t technically an input since it’s wrapped in a form, but I did it so I could have an easy way to use the submit button with the input.

Our Test File

Assuming you already have jest installed in your app, we will also be installing a few other libraries. Here’s what the top of your test file will look like:

MyInput.test.js

  • We will be getting a few extra and helpful assertions from
  • Because mounts our components to , we will be using its which will unmount and clean up our DOM after each
  • I will go into detail about 's and functions later on

Our Test Utilities

render

The method is the bread and butter of RTL. In this following example, I’m showing what RTL’s method is doing under the hood. My next example (getByLabelText) will demonstrate how to use in our tests.

What RTL’s render method sort of looks like

  • The component that’s passed to it gets rendered on a
  • RTL’s utility methods (ie. , etc) are all extracted and assigned to
  • These methods are then spread on the object that is returned so that we can destructure out the utilities that we need

getByLabelText

The utility works well for getting an element and its associated .

  • 's function mounts our component to the DOM and exposes a handful of utility methods ( in this case)
  • We pass the text of the label to and it returns the that was associated with the
  • We then make an assertion on the that it has an attribute

Note: works here because of the relationship we created between the and in . The in the element matches the in the element.

If we didn’t create the association between and or made a typo in the like calling it , RTL would throw us an error:

RTL’s enforcement of this relationship is great for catching typos and keeping accessibility high!

getByTestId

This is a method I really like about RTL. Remember that attribute we added to ? The utility is what we’ll use to target it.

  • We use RTL’s method to find an element with a
  • Because of where we set min = 1-->, and our initial state where we set — we expect our button to be disabled since our input is not valid

Typically, you’ll want to use when targeting a specific element — it’s great for looking up inconspicuous or other elements in weird places.

getByText

This is a pretty general method that looks for any matching text that’s passed as an argument.

This is the same test we did with except here we use and pass it the button text “Submit!”

I tend to prefer using & attributes over , as the former method reduces the scope and allows me to pinpoint the exact element I’m asserting.

fireEvent

The method is used to manipulate the DOM based on some sort of event. The list of events spans from to (the full list can be viewed here).

  • First, we assign and variables to the elements that we found using the methods we used earlier
  • Next, we call and pass it an object that is similar to the one we used in our function in

handleChange in MyInput.js

  • In our function we set the number state with , so in the same fashion with our test we pass it the object
  • Finally, we expect that the button is enabled because any number greater than or equal to our (which is set as 1 from our ) should be a valid input, thus enabling the button

rerender & queryByTestId

This is a two-parter to show how you can test for something to NOT be in the DOM.

For , I typically use it to assert the outcome of a component after updating its props.

is very similar to . The difference between the two is that will throw an error when it can’t find the element and will return . All the other .. methods have similar .. methods.

  • Just like before, we assign the variable to the element associated with the label text “Number:”
  • We call and change the input to
  • The input’s value is now less than our which is defined in our , so we expect the to be present
  • Now we call on our component and update the prop to
  • Now that our input’s value , is greater than our updated , we expect the to not exist ()

Bonus: debug

RTL’s method will log out the current structure of the DOM where ever you place it. Very useful for peeking at the state of your DOM during different parts of your tests.

  • Deconstruct out of the method
  • Here we place it before and after we call
  • Run our test and we’ll see this in our console:
Two debugs, two outputs!

  • In the first output, before we call we see our input’s
  • In the second output, after we call we see that our input’s and there’s also the inclusion of an now

Conclusion

Hopefully, this walkthrough gave you a good idea on how you can implement RTL in your next (or even current) project. It’s a great way to test your components in a way that mimics your users’ interactions.

All of the examples can be found on github, or you can try it out instantly on repl.it.

If you have any other testing tips, don’t be afraid to send them my way! I just started my testing journey and would love to learn more about how to make my application more bulletproof. Also, be sure to stay tuned for the next part of this series where I will dive even deeper into testing! 🐐

More about:

Jeffery Zhen