javascript
testing
react
codenewbie
Published: Thu May 13 2021 (~ 8 min)
Read on Dev.toIn my last post, A Beginner's Guide to Unit-testing with Jest, I walked through getting started with testing in javascript using the Jest testing library. Here, I hope to expand on what was already discussed about matchers and expectations, and the purpose of test implementation with an example of how to write basic tests for React components.
Writing tests for React components with Jest follows the same similar structure of a describe
function containing test
blocks with expect
functions and matchers. However, instead of testing the functionality of individual JS functions, we need to ensure that React components are rendering properly and that user interactions with the component occur as expected. For a detailed guide on the basic setup for Jest testing and it purposes, please see my previous post, A Beginner's Guide to Unit-testing with Jest.
We will walk through the process of setting up a basic React App with interactive elements such as a counter with increment/decrement buttons, and a form to post text to the DOM. I will walk through writing the Jest tests and the rest of the code here, but you can view the repo containing all of the code as well.
Steps:
cd
into that directory.npx-create-react-app
, along with the React Testing Library. The React Testing Library provides additional functions to find and interact with DOM nodes of components. No additional installation or setup is needed when beginning your React app this way.[object Object]
When a new React app is created using npx-create-react-app
, the App.js
file comes pre-filled with placeholder content and a test file is included for this by default - App.test.js
. Let's walk through what happening in this test file:
[object Object]
render
and screen
.Render
is a function that will build the DOM tree in memory that would normally be rendered as a webpage. We will use this to turn our component code into the format that the user would be interacting with.Screen
is a an object with a number of querying functions that will allow us to target element(s) in the DOM. For comparison, it functions similarly to querySelector
, however the syntax is a bit different since we will not be using an element's tag/class/id.The next import, userEvent
will allow us to simulate a variety of user actions with a targeted element, such as button presses, typing, etc.The full documentation for userEvent can be found here
The third import, @testing-library/jest-dom/extend-expect
, provides additional matchers that we can use for targeted elements. The full documentation for Jest-DOM can be found here
Lastly, we need to import the component that we will be testing in this file.
With these imports completed, we see the familiar structure of a Jest test function.
[object Object]
string
argument describing the test, and a callback function with the test content.getByText
function of the screen
object is invoked with a regular expression argument. The getByText
function will return the first element in the DOM that has text matching the regular expression, which will then save to a variable for later use.expect
and matcher statements. In this case, we are simply stating that we expect that our previous query found an element in the document.If we start the app on the local machine using npm start
we can see that the specified link text is clearly visible, and the default test should pass.
We can confirm that the default test is working before we move on to writing our own by running npm test
in the console.
Following Test-Driven Development, let's begin by defining what our App should do, write the tests for it, and then implement the code that should pass the tests.
There will be two buttons: increment and decrement.
When clicked, they should increase/decrease a counter on the page.
The counter should never be negative, so the decrement button should be disabled when the counter is less than 1.
There should be a form with an input field and a submit button.
The user should be able to type into the field, and when submitted, the text from the field will display in a list on the screen.
Each list item will have a "remove" button, that when pressed should remove that item from the screen.
Since the counter value will just be a number, I wanted to ensure that the query matches the counter value and not another number that is potentially on the page (as may happen with just using getByText()
). For this, we can use the dataset attribute data-testid
similar to how we use id
in HTML. The difference is that data-testid
is strictly for testing purposes and not related to CSS or other interactions.
In this first test, I wrote the expectation statements to match the initial plan for the counter feature. We expect the DOM to include both buttons, the counter label "Counter: ", and the value of the counter. We would also expect that when the page is first loaded, the counter has a default text value of 0, and because of this, our decrement button should be disabled to not allow a negative counter value.
[object Object]
For the counter, we expect that each time the increment button is pressed, the counter value should increase by 1. When the counter goes above zero, the decrement button should become enabled. To simulate a button press, we use the click()
function in the userEvent
object we had imported earlier.
[object Object]
The second feature of our mini-app, to explore how we can test for user interaction with a form, involves a form that creates list items when submitted.
First, we can create the basic test to ensure that the expected elements are rendered to the page, similar to what was done earlier.
[object Object]
Now that we have confirmed that the elements exist, we need to ensure that they function as expected:
[object Object]
After a list item has been created, the user should be able to click the remove button next to it, to remove it from the page.
[object Object]
With the tests in place, we should now build our component, and it should meet the expectations set in our tests. Writing the code for the component is no different than it would be without the tests in place. The only additional thing we must do, is include the data-testid
on the elements for which our tests were querying the elements by using getByTestId()
such as the list items and buttons. The full code implemented to create the component can be found below the demo.
End Result:
We can now run the tests using npm test
as see the results!
Below is the code used to create the component demonstrated above, using hooks:
[object Object]
While this only scratches the surface of testing React components, I hope this serves as a primer for getting started with developing your own tests for your components.