React Testing Beginner's guide | RTL & Jest Part - 1
Testing react application made easy in simple steps. Understanding basic concepts of Jest and React Testing Library
Table of contents
If you are a person who just finished Learning React Framework and got fairly good at it. You start giving Interviews, You almost got the skills for developing Single Page applications. You are having a decent conversation with the interviewer until he asks you
“How do you test your Application?”.
This Blog covers the basic understanding of Testing in React.
Why do we need to test?
As a developer, our primary goal is to build software that WORKS!
It doesn’t matter how much thought we put into improving user experience and performance if it doesn’t work. We check if our software works as it’s expected.
What is Jest? When did I install its dependencies?
Jest is a javascript testing framework
Jest is a test runner that finds tests, runs the tests, determines whether the tests passed or failed, and reports it back in a human-readable manner.
If you wanna know how Jest is written: The blog by kent C. Dodds
React out of the box comes with jest and RTL with useful defaults. If we look at package.json
inside the project folder, we see testing-library package dependencies.
"dependencies": {
"@testing-library/jest-dom": "^5.16.5",
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^14.4.3"
....
"react-scripts": "5.0.1",
}
We don’t see jest package? However, we do see the react-scripts package. If we open the node_modules folder that pins down the react-scripts package.json. We see Jest as its dependency.
So in the node_module folder, Jest does exist even though it is not a direct dependency created with create-react-app
.
What is React Testing Library?
It is a javascript utility that provides virtual DOM for testing React Components.
There's no virtual DOM while running a test. So the React Testing library provides a virtual DOM which we can use to interact with and verify the behavior of a react component.
The Core library is called DOM Testing Library and RTL is simply a wrapper around this core Library to test React applications in an easier way.
Types of test
Unit Tests
Integration tests
End to End tests
What is a test?
An Automated test is a code that throws an error when the actual output does not match the expected output.
Your first test
Create a new component in your freshly installed create-app inside src/components/HelloWorld.
Let’s name it Helloworld.tsx
export const HelloWorld = () => {
return <div>Hello World</div>
}
Now let’s write a test for it. In the same directory create a new file with the name Helloworld.test.tsx
Now let’s remember all we need is a test method that is globally available in create-react-app project
test() method has two parameters:
First Parameter: string which can be the name of the test
Second Parameter: callback function which is run for checking the expected and actual output
Inside the second parameter. Call render method from testing-library with the Helloworld.tsx as a component.
Now when we have a virtual DOM let’s check if the text Hello World is present.
import screen at the top and use getByText available on the object and let’s catch it in a constant helloworldElement.
Finally, we can use expect method from jest to test our assertion.
import {render,screen} from "@testing-library/react"
test('Helloworld renders successfully', () => {
render(<Helloworld />) // this is where a virtual DOM is created
const helloWorldElement = screen.getByText('Hello World')
expect(helloWorldElement).toBeInTheDocument()
}
Now in the terminal run npm run test
or yarn test
A test is useless if it doesn’t fail. So Let’s make a small change in the component to check if the test is running as expected.
export const HelloWorld = () => {
return <div>Hello World of testing</div>
}
RTL Queries
Let’s get a little deep into RTL, Starting with the RTL Queries.
Queries are the methods that Testing Library provides to find elements on the page.
To find a single element on the page. We have
getBy..
queryBy..
findBy..
To find multiple elements on the page. We have
getAllBy..
queryAllBy..
findAllBy..
each of the methods can be suffixed to form an actual query.
The suffix can be one of Role, LabelText, PlaceholderText, Text, DisplayValue, AltText, Title, and finally TestId.
Let’s start with the single element "getByRole"
getByRole queries for elements with the given role.
Role refers to the ARIA (Accessible Rich Internet Applications) role which provides semantic meaning to the content to ensure people using assistive technologies are able to use them.
Let’s look at an example of the semantic roles of HTML elements
Button element has a button role
Anchor element has a link role
h1 to h6 elements have a heading role
checkboxes have a checkbox role
radio buttons have a radio role, and so on…
if you’re working with elements that do not have a default role or if you want to specify a different role, the role attribute can be used to add the desired role. example navbar links can have button role=’button’.
Let's look at another example. We have a fairly simple component that renders a form
const Form = () => {
return (
<form>
<div>
<label htmlFor="name">Name</label>
<input type="text" id="name" />
</div>
</form>
)
}
export default Form
It’s corresponding test that asserts if the given textBox input renders
here we make use of the getByRole query from RTL.
import { render, screen } from '@testing-library/react'
import Form from './Form'
test('Form renders correctly', () => {
render(<Form />)
const nameInputElement = screen.getByRole('textbox')
expect(nameInputElement).toBeInTheDocument()
})
Now let’s add more different types of input in the form.
const Form = () => {
return (
<form>
<div>
<label htmlFor="name">Name</label>
<input type="text" id="name" />
</div>
<div>
<input type="checkbox" />
</div>
</form>
)
}
export default Form
When two or more tests are there for a component we can combine the tests inside a describe method. describe also takes two parameters, the first is the string, and the second is the callback function.
This time we make use of the ‘checkbox’ property of getByRole method.
Watch for the terminal and it succeeds
import { render, screen } from '@testing-library/react'
import Form from './Form'
describe('Form', () => {
test('Renders correctly', () => {
render(<Form />)
const nameInputElement = screen.getByRole('textbox')
expect(nameInputElement).toBeInTheDocument()
})
test('Renders a checkbox correclty', () => {
render(<Form />)
const checkboxElement = screen.getByRole('checkbox')
expect(checkboxElement).toBeInTheDocument()
})
})
Priorities while using RTL queries
getByRole
getByLabelText
getByPlaceholderText
getByText
getByDisplayValue
getByAltText
getByTitle
getByTestId
Your first priority to access should always be getByRole , if for a reason getByRole can't be used. Look for getByLabelText, it really helps working with forms. Followed by the rest of the methods. If in case nothing can be used the last option is to use getByTestId.
Now let’s look at multiple elements “ getAllByRole.. ”
Let’s start with a simple component that lists downs the name of cities and their corresponding tests
type CitiesProps = {
cities: string[]
}
const Cities = (props: CitiesProps) => {
const { cities } = props
return (
<div>
<h1>Famous cities in the world</h1>
<ul>
{cities.map((city: string) => {
return <li key={city}>{city}</li>
})}
</ul>
</div>
)
}
export default Cities
import { render, screen } from '@testing-library/react'
import Cities from './Cities'
describe('Cities', () => {
const cities = ['Paris', 'New York', 'London', 'Bangkok']
test('renders correctly', () => {
render(<Cities cities={cities} />)
const headingElement = screen.getByRole('heading', {
name: 'Famous cities in the world',
level: 1,
})
expect(headingElement).toBeInTheDocument()
})
test('renders a list of cities', () => {
render(<Cities cities={cities} />)
const listBoxElement = screen.getByRole('list')
expect(listBoxElement).toBeInTheDocument()
})
test('renders a list items of cities', () => {
render(<Cities cities={cities} />)
const listItemsElement = screen.getAllByRole('listitem')
expect(listItemsElement).toHaveLength(cities.length)
})
})
This is a small blog that should get you started with testing components in react. In the Next blog let’s look at how to mock user events in react tests.
Thank you for reading, I hope this helps in your journey.
Keep rocking, Keep coding 💻