Katie N
Posted on February 29, 2024
When creating applications, a skill that will help create scalable and properly working code is the ability to write tests. Unit testing allows us to test the individual functionality of our React components. Learning how to write tests can be instrumental in debugging quicker and writing good dynamic code. In this post we're going to talk about how to use Jest testing framework with React. If you're familiar with React, you know how powerful and intuitive it is. Well, Jest is just like that - powerful and intuitive. We will be using the Node package manager for this blog but yarn works similarly.
Getting Setup
When starting a React project, a popular way to go about it is to run npm create-react-app
. Starting your repo with this command will automatically install Jest so there is no need for any extra install/download work. Double check your package-json
folder to make sure you have the proper packages installed. If it is not already installed you can run npm install --save-dev @testing-library/react
to install.
"name": "client",
"version": "0.1.0",
"private": true,
"proxy": "http://localhost:3000",
"dependencies": {
--->"@testing-library/jest-dom": "^5.17.0",
--->"@testing-library/react": "^13.4.0",
--->"@testing-library/user-event": "^13.5.0",
"bootstrap": "^5.3.3",
"react": "^18.2.0",
"react-bootstrap": "^2.10.1",
"react-dom": "^18.2.0",
"react-router-dom": "^6.22.1",
"react-scripts": "5.0.1",
"web-vitals": "^2.1.4"
},
"devDependencies": {
"@babel/plugin-proposal-private-property-in-object": "^7.21.11"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
--->"test": "react-scripts test",
"eject": "react-scripts eject"
You can see under our "dependencies" there is the Jest package installed and the "test" line in our "scripts".
Test Structure
The best way to set up your test files is to create a folder in your /src
folder and name it __tests__
. In this folder you will keep your test files. When you create your file, the best practice is to name your test file almost identical to your component file name. For example, if we were going to start writing tests for our App.js
file, we should name our test file App.test.js
.
Navigate to App.test.js
and in the top of the page import the libraries you need for testing. It will typically look something like --
// import libraries needed for testing
import "@testing-library/jest-dom";
import { render, screen } from "@testing-library/react";
import React from "react";
// import the component you wrote
import App from "../components/App";
Often the test file will already be started for you automatically, but you should always double check.
Jest tests are written in test blocks. They consist of 4 parts.
- Render the component you want to test.
- Find elements of the component you want to interact with.
- Interact with the element and establish expectations.
- Assert 'expected' results.
For this example, let's use the tried and true "Hello World" example. We have a simple App.js
component --
import React, { useState } from 'react';
function App() {
const [message, setMessage] = useState('Hello World');
return (
<div>
<p data-testid="message">{message}</p>
<button onClick={() => setMessage('Button Clicked')}>Click Me</button>
</div>
);
}
export default App;
And then we will have our App.test.js
file --
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import App from './App';
test('renders the correct initial message and updates it on button click', () => {
// Render the App component
render(<App />);
// Find the element that displays the message
const messageElement = screen.getByTestId('message');
expect(messageElement).toHaveTextContent('Hello World'); // Initial expectation
// Find the button and click it
const buttonElement = screen.getByRole('button', { name: 'Click Me' });
fireEvent.click(buttonElement);
// Assert the message has changed after clicking the button
expect(messageElement).toHaveTextContent('Button Clicked'); // Updated expectation
});
Let's go through each section.
test('renders the correct initial message and updates it on button click', () => {
// Render the App component
render(<App />);
Each test will start with test()
or it()
(used interchangeably) to provide context around what you plan to use the test for. You can also take it a step further and include a describe()
above the test()
line to further describe the test or name it.
describe("correctMessage", () => {
test('renders the correct initial message and updates it on button click', () => {
// Render the App component
render(<App />);
Next up we find the elements we want to interact with.
// Find the element that displays the message
const messageElement = screen.getByTestId('message');
// Initial expectation
expect(messageElement).toHaveTextContent('Hello World');
We will use screen
to filter through the component and pluck out what we're wanting to test. We're looking for .getByTestId
so let's look back up at our App.js file and see what is being pulled into our test.
In our App.js file we see this line --
<p data-testid="message">{message}</p>
This is what is being pulled by our test to be interacted with.
In the next line we are declaring what we initially expect from this element. We expect the message to say "Hello World".
Then we go down a little in our test and see the next expectation of our test.
// Find the button and click it
const buttonElement = screen.getByRole('button', { name: 'Click Me' });
fireEvent.click(buttonElement);
Same thing as before. Pulling the element we want to test through screen
and declaring our initial expectation. This is the element we are pulling this out of our App.js
component --
<button onClick={() => setMessage('Button Clicked')}>Click Me</button>
We are expecting this button to be able to do something. To click.
note: the fireEvent
function is used to mimic a call to action for your test.
Once we have pulled our elements and gave them initial expectations, we're going to assert what we want our final expectation to be of how those elements tie together to do what they're supposed to.
// Assert the message has changed after clicking the button
expect(messageElement).toHaveTextContent('Button Clicked'); // Updated expectation
We expect our button to display "Hello World" when it is pressed.
Refactoring and npm test
Once you have your tests written you'll want to actually implement them. Assuming you have already ran npm install
in your terminal, you're going to run npm test
. You should receive an output of all your passing and failing tests as well as a CLI menu to be able to interact with your terminal while your tests are being watched and ran.
> your-app-name@1.0.0 test
> jest
PASS src/App.test.js
✓ renders the correct initial message and updates it on button click (32 ms)
Test Suites: 1 passed, 1 total
Tests: 1 passed, 1 total
Snapshots: 0 total
Time: 1.357 s, estimated 2 s
Ran all test suites.
If you notice that your tests are failing, you will have to go back into your App.js
file to refactor your code until your tests are passing. Unless you exit out of the test suite it will update and run every time you save your file.
To be able to navigate through the test suite here is the CLI menu as well.
Watch Usage
› Press a to run all tests.
› Press f to run only failed tests.
› Press p to filter by a filename regex pattern.
› Press t to filter by a test name regex pattern.
› Press q to quit watch mode.
› Press Enter to trigger a test run.
Conclusion
I hope now you have a better understanding of how to implement testing into your code. It is so helpful and time saving in the debugging process allowing you to focus on the creativity.
Sources
Digital Ocean
Smashing Magazine
BrowserStack
NetNinja
ChatGPT
Canvas
Posted on February 29, 2024
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.
Related
January 16, 2024