An introduction to testing react applications using @testing-library/react by creating a simple todo application
Testing applications is really important before shipping applications to your end-users since it confirms that everything is working correctly before deployment happens. It also gives confidence that your users get correct outcomes when using your application. Nobody wants frustrated users anyway,
You can throw paint against the wall and eventually you might get most of the wall, but until you go up to the wall with a brush, you’ll never get the corners. 🖌️
Here’s a quick tour of the kind of testing types that do exist when testing applications.
- End To End tests
- Integration tests
- Unit tests
- Static tests
End To End tests are tests written to mimic user behaviour around the application to verify that all functions work as intended. Integration tests are tests written to verify that several units work together in harmony. Unit tests are written to verify that isolated functions/parts work together as expected. Static tests catch typos and type errors as you write code.
For this tutorial, we will focus on writing a unit test to verify that our todo application works as we intend it to. So let’s write some code.
On the terminal, boot up a react application using create-react-app. We will call this application simple-todo-list-1
npx create-react-app simple-todo-list-1
If you have VScode installed and configured to open from the terminal type in
cd simple-todo-list-1
code .
This opens the usual file structure of a react application created using create-react-app. If the above did not work, make sure you have VScode installed correctly and refer to this if you want to open VScode from your terminal. We are going to write some code and explanations will come later.
On VScode, click on the App.js file found under the src directory
Add the following code inside App.js
import "./App.css"function App() { return ( <div className="App"> <h1>Here is a list of Items</h1>
<form> <input id="itemName" type="text" required /> <input type="submit" value="add item" /> </form> </div> )}export default App
In the terminal where we ran create-react-app, let’s see the initial page of our application by running the npm command below
npm start
We have a form displayed on the page, we will use the form to add items to our list. Great!
So because we need to add lists, we need to manage state too. In react we use the useState hook to manage state for us. So let’s bring the useState hook in at the top of our App.js file
import { useState } from "react"
Above the return in the App.js file add the following,
const [ itemList, setItemList ] = useState([])
We initialize the item list to an empty array. For this application, an item will be an object that will contain the item name and an item id. It will look like something close to this
{itemID: "c44c7d53-1dcf-4a56-86ab-d634c03f379f", itemName: "item 1"}
Don’t you worry about the cryptic itemID. We’ll get to that.
As far as we have come, there is currently no way to add an item on the itemList array so let’s fix that.
This tutorial is simple so we use the simple solutions available for us when using forms in react. We will use react-hook-form.
So in the terminal, type in
npm i react-hook-form
Let’s bring in the power of react-hook-form into our application. At the top of our App.js file, import react-hook-form like so,
import { useForm } from "react-hook-form"
We have imported the useForm ‘hook’ that gives us some helpful functions to help us when using forms in react. We need a way to submit our form, monitor the input field and eventually reset the input field after the user is done submitting their item name. Just below the function name for our App function name, add this
const { register, handleSubmit, reset } = useForm()
So we currently have the following code so far,
import { useState, useEffect } from "react"import { useForm } from "react-hook-form"import "./App.css"
function App() {const { register, handleSubmit, reset } = useForm()const [itemList, setItemList] = useState([]) return ( <div className="App"> <h1>Here is a list of Items</h1> <form> <input id="itemName" type="text" /> <input type="submit" value="add item" /> </form> </div> )}export default App
Cool, let's now utilize the register, handleSubmit that we get from the react-hook-form.
So within the input field add the following code (new code in bold)
<input
id="itemName"
type="text"
{...register("itemName")}
required
/>
We can now reference the input fields’ input after ‘registering’ it with register from the register function that react-hook-form provides and we mark the input field as required because we do not want the user submitting empty input values 🤗.
Since we want the user to hit submit and have their item added to the item list let's refactor the form element to this, (new code in bold)
<form onSubmit={handleSubmit(onSubmit)}>
Above, we have the onSubmit function being passed as an argument to the handleSubmit function but we haven’t created it yet, so let’s do it below, in the body of the App function just above the return add the following function
const onSubmit = (data) => { console.log(data) setItemList([ ...itemList,{ itemName: data.itemName, itemID: uuidv4(), }, ]) reset()}
Before we go any further, have you noticed the uuidv4() stuff in the code above, it comes from a library we have to fetch from npm called uuid. uuid helps us have unique chars uniquely identify items in our item list. So very fast, let’s add uuid to our app and make sure to import it
$ls
README.md node_modules package-lock.json package.json public src
$npm i uuid
Close to the top of the App.js file bring in uuid like so,
import { v4 as uuidv4 } from "uuid"
Now, If you now add an item to the form on the browser where we have the application running (if you had shut it down just do npm run start), you should see something on your dev console(open this with CTRL + SHIFT + I for windows or CMD + OPTION+ I for macs)
Great! Let’s keep going,
We will now add a component that will handle showing the list items on the screen. So on the terminal at the root of the application, (where we did cd simple-todo-list-1) we will create a components folder that will contain an ItemList.js file which we call a component in React
$ls
README.md node_modules package-lock.json package.json public src$cd src$mkdir Components $cd Components$touch ItemList.js
Back to VScode, click on the Components at the left window. Like so
Double click on ItemList.js file to open it and then add the following code to it
import React from "react"
const ItemList = ({ itemList }) => { if (itemList.length === 0) { return <div>No item in the list</div> } return ( <div> <ul style={{ listStyle: "none" }}> {itemList.map((item) => ( <li key={item.itemID}> {item.itemName} </li> ))} </ul> </div> )}export default ItemList
The ItemList component takes on the list of items as a prop, we have destructured the props to pluck out the item list as itemList which is an array of list items. The component is simply looping through the array of items and displaying each item to the page.
We bring in this ItemList component into the App.js file as so,
import ItemList from "./Components/ItemList"
and at the bottom of the form element in App.js file add the following code
<ItemList itemList={itemList} />
Our todo list application would look so much more dynamic if we not only had the ability to add items but to remove them also, so let’s add a handleRemove function to handle removing items from the list of items for us.
So in the body of the App.js main function above the main return, add this
const handleRemove = (itemID) => { const newList = itemList.filter((item) => item.itemID !== itemID) setItemList(newList)}
This function removes items in the items list based on the item name passed as an argument to the handleRemove function.
We utilize this function in the ItemList component by adding it as a prop like so,
<ItemList itemList={itemList} handleRemove={handleRemove} />
In the ItemList.js file, pluck out the handleRemove function from the props by destructuring like so,
const ItemList = ({ itemList, handleRemove }) => {
and we utilise it by adding a button to each item added to the list whenever we want to remove it from the list like so (new code in bold)
return ( <div> <ul style={{ listStyle: "none" }}> {itemList.map((item) => ( <li key={item.itemID}> {item.itemName}{" "} <span> <button onClick={() => handleRemove(item.itemID)} style={{ cursor: "pointer" }} > ❌ </button> </span> </li> ))} </ul></div>)
What if we do not have any items in the list, we can show that in the ItemList component by merely checking the length of the itemList array like so
if (itemList.length === 0) { return <div>No item in the list</div>}
So that our final ItemList component looks like so
import React from "react"
const ItemList = ({ itemList, handleRemove }) => { if (itemList.length === 0) { return <div>No item in the list</div> } return ( <div> <ul style={{ listStyle: "none" }}> {itemList.map((item) => ( <li key={item.itemID}> {item.itemName}{" "} <span> <button onClick={() => handleRemove(item.itemID)} style={{ cursor: "pointer" }}> ❌ </button> </span> </li> ))} </ul> </div> )}export default ItemList
If you would want to monitor changes, in the itemList array on the browser dev console, you can do that by using useEffect hook which monitors the change in state in a react application and you can do this using the code below,
useEffect(() => {console.log(itemList)}, [itemList])
Yes, you have to bring in useEffect from react at the top of your App.js file like so
import { useState, useEffect } from "react"
Great. Let’s now get into the testing
In VScode click on the App.test.js file to open it
Now before we do some testing, let’s bring in the components we want to test and some functions to help us with testing from the @testing-library/react library. So in the App.test.js file add the following,
import { render, screen } from "@testing-library/react"import App from "./App"import ItemList from "./Components/ItemList"
the @testing-library/react comes in when we do create-react-app command so we do not have to bring in any packages.
Let’s write a test that checks of the header element shows correctly when the application first renders
test("renders the application header element correctly", () => {. render(<App />)const headingElement = screen.getByText(/Here is a list of Items/i)expect(headingElement).toBeInTheDocument()})
To see the result for this head to the terminal and do,
$ls
README.md node_modules package-lock.json package.json public src
$npm run test## press a to run all tests
## your test results should appear here
Great, let’s add a test to check if the ‘No item in list’ appears when the application first renders. Below the test written above, write this
test("renders no item in list on first render", () => {render(<App />)const noItemMessage = screen.getByText(/No item in the list/i)expect(noItemMessage).toBeInTheDocument()})
You can check the results in the terminal as we did earlier on.
We can add some more tests, so below the tests written above include the following tests.
test("renders 'No item in list' when itemlist is empty", () => {render(<ItemList itemList={[]} />)expect(screen.getByText(/No item in the list/i)).toBeInTheDocument()})test("renders list of items correctly", () => {render(<ItemListitemList={[
{
itemID: "c44c7d53-1dcf-4a56-86ab-d634c03f379f",
itemName: "item 1"
}, {
itemID: "610c41c8-8a54-4ec5-b988-9a41a72f9e45",
itemName: "item 2"
}, {
itemID: "2e4403a2-60d0-408e-ba4d-1e0f69c5e2ab",
itemName: "item 3"
},]} />)expect(screen.getByText(/item 1/i)).toBeInTheDocument()expect(screen.getByText(/item 2/i)).toBeInTheDocument()expect(screen.getByText(/item 3/i)).toBeInTheDocument()})
The first test shown immediately above confirms that the ItemList component show that there is no item in the list when the itemList is an empty array and the final test shows that when the ItemList component receives an itemList prop containing list of items, they do correctly show on the page.
If this is your first reactjs test, congratulations, 🥳. I hope you learnt something from this tutorial. If you want to know more visit testing-library/react.
Get the code for this tutorial here
Thanks for reading 👏🏼