This content originally appeared on HackerNoon and was authored by keploy
Testing is checking if your code works the way it's supposed to. When you write a program, you have an idea of what it should do. Testing is the process of making sure it does that. It's like double-checking your work.
\ In this article, we're diving into the world of React testing using two powerful tools: Jest and React Testing Library. We'll explore how these tools work together to create robust, reliable tests for your React applications.
Importance of Testing in React Development
Testing is a crucial step in the development process of React-based applications, similar to any other software development.
React uses a component-based structure, meaning a single change in one component could potentially affect others. This interdependence is why testing is so important in React.
\ In case a component fails the test, you will be notified immediately, allowing for quick fixes and minimizing work stoppage.
Introduction React Testing Library
React Testing Library is a helpful tool for testing React components. It helps you test your React components in a way that's close to how a user would interact with them.
\
It is built on top of DOM Testing Library
, which is a very lightweight solution for testing DOM nodes (whether simulated with JSDOM
as provided by default with Jest or in the browser). It creates a simulated web page (a DOM) for each of your components, which involves querying the DOM for nodes in a way that's similar to how the user finds elements on the page.
Introduction to Jest
Jest is a complete and easy-to-set-up JavaScript testing solution. It's created and maintained by Facebook.
\ It includes Test Runner, Assertion Library, and Mocking features. Jest provides a way to check if values meet certain conditions with the Assertion library. Mocking allows jest to create fake versions of functions or modules for testing.
Setting Jest and React Testing Environment
In this section, we’ll set up a simple React app with create-react-app
for our testing environment using Jest and React Testing library. You can choose vite
any other react framework.
\ Feel free to clone the sandbox below and follow along
Setting up CRA
npx create-react-app jest-testing-blog
cd jest-testing-blog
\ CRA comes preconfigured with a jest testing environment, for other frameworks, we might need to install it manually.
npm install --save-dev jest @testing-library/react @testing-library/jest-dom
Filename Conventions
Jest will look for test files with any of the following popular naming conventions:
- Files with
.js
suffix in__tests__
folders. - Files with
.test.js
suffix. - Files with
.spec.js
suffix.
\
The .test.js
/ .spec.js
files (or the __tests__
folders) can be located at any depth under the src
top-level folder.
For larger projects it's recommends to put all
.test.js
/.spec.js
files in__tests__
folders undersrc
folder
Project Overview
To demonstrate the Jest and React Testing Library, we would be using a simple Todo application.
\ Features of the Todo app:
- Add new todo items
- Mark todo items as complete
- Delete todo items
- Filter todos (All, Active, Completed)
\ This project is small enough to be manageable but includes enough functionality to showcase various testing scenarios.
\ The app will include the following components:
TodoList: The main component that holds the state and renders other components
TodoItem: Represents a single todo item
AddTodo: A form to add new todos
FilterTodos: Buttons to filter the todo list
Understanding Jest Syntax
Jest provides a simple structure for writing test cases.
describe('Group of tests', () => {
test('individual test', () => {
// Test code
});
});
describe
: Groups related tests together. Useful when we are doing integration testing which consists of multiple unit testing.test
: Defines an individual test case.
\ Jest provides various assertion methods to check if values meet certain conditions:
expect(value).toBe(expectedValue);
expect(value).toEqual(expectedValue);
expect(value).toBeTruthy();
expect(array).toContain(item);
\ Jest also allows to run functions before and after executing test cases.
beforeEach(() => {
// Runs before each test in this file
});
afterEach(() => {
// Runs after each test in this file
});
\ Jest also supports asynchronous code
test('async test', async () => {
const data = await fetchData();
expect(data).toBe('peanut butter');
});
Writing Your First Jest Test Case
Let's write a simple jest test for your Counter component using Jest and React Testing Library. This test will check if the component renders correctly and if the increment and decrement buttons work as expected.
\
Create a Counter.test.js
under src/components/__test__
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import { Counter } from './../Counter.js';
describe('Counter Component', () => {
// First Test Case
test('renders initial count of 0', () => {
render(<Counter />);
const countElement = screen.getByText(/Counter: 0/i);
expect(countElement).toBeInTheDocument();
});
// Second Test Case
test('increments count when increment button is clicked', () => {
render(<Counter />);
const incrementButton = screen.getByText(/Increment/i);
fireEvent.click(incrementButton);
const countElement = screen.getByText(/Counter: 1/i);
expect(countElement).toBeInTheDocument();
});
// Third Test Case
test('decrements count when decrement button is clicked', () => {
render(<Counter />);
const decrementButton = screen.getByText(/Decrement/i);
fireEvent.click(decrementButton);
const countElement = screen.getByText(/Counter: -1/i);
expect(countElement).toBeInTheDocument();
});
// Fourth Test Case
test('correctly updates count with multiple clicks', () => {
render(<Counter />);
const incrementButton = screen.getByText(/Increment/i);
const decrementButton = screen.getByText(/Decrement/i);
fireEvent.click(incrementButton);
fireEvent.click(incrementButton);
fireEvent.click(decrementButton);
const countElement = screen.getByText(/Counter: 1/i);
expect(countElement).toBeInTheDocument();
});
});
This jest test case file does the following:
- It imports the necessary testing utilities from react testing and the Counter component.
- Jest uses a
describe
block to group-related jest tests for the Counter component. - The first jest test case checks if the initial count is rendered as 0.
- The second test case clicks the increment button and checks if the count increases to 1.
- The third test clicks the decrement button and checks if the count decreases to -1.
- The fourth test performs multiple clicks on both buttons and checks if the final count is correct.
\ We can usually run the test cases using jest with:
npm test
\ which test all our test cases and return something as below:
PASS src/components/__test__/Counter.test.js
Counter Component
√ renders initial count of 0 (7 ms)
√ increments count when increment button is clicked (3 ms)
√ decrements count when decrement button is clicked (3 ms)
√ correctly updates count with multiple clicks (3 ms)
Test Suites: 1 passed, 1 total
Tests: 4 passed, 4 total
Snapshots: 0 total
Time: 0.451 s, estimated 1 s
Ran all test suites related to changed files.
Now that we know how jest and its syntax work, let's move into writing test cases for our simple todo application.
Let's Start Writing Testcases
TodoList
Create a TodoList.test.js
under src/components/__test__
\ This test file contains four test cases for a TodoList component. Let's break down what each test case does:
Test: Adds a New Todo
- Finds the input field and "Add" button
- Simulates typing "New todo" into the input field
- Simulates clicking the "Add" button
- Checks if "New todo" appears in the document, confirming the todo was added
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import TodoList from './../TodoList.js';
test('adds a new todo', () => {
render(<TodoList />);
const input = screen.getByPlaceholderText('Add a new todo');
const button = screen.getByText('Add');
fireEvent.change(input, { target: { value: 'New todo' } });
fireEvent.click(button);
expect(screen.getByText('New todo')).toBeInTheDocument();
});
Test: Marks a Todo as Complete
- Finds the checkbox associated with the new todo
- Simulates clicking the checkbox
- Checks if the checkbox is now checked, confirming the todo was marked as complete
test('marks a todo as complete', () => {
render(<TodoList />);
const input = screen.getByPlaceholderText('Add a new todo');
const addButton = screen.getByText('Add');
fireEvent.change(input, { target: { value: 'New todo' } });
fireEvent.click(addButton);
const checkbox = screen.getByRole('checkbox');
fireEvent.click(checkbox);
expect(checkbox).toBeChecked();
});
Test: Deletes a Todo
- Finds the "Delete" button associated with the new todo
- Simulates clicking the delete button
- Checks if "New todo" is no longer in the document, confirming the todo was deleted
test('deletes a todo', () => {
render(<TodoList />);
const input = screen.getByPlaceholderText('Add a new todo');
const addButton = screen.getByText('Add');
fireEvent.change(input, { target: { value: 'New todo' } });
fireEvent.click(addButton);
const deleteButton = screen.getByText('Delete');
fireEvent.click(deleteButton);
expect(screen.queryByText('New todo')).not.toBeInTheDocument();
});
Test: Filters Todos
- Adds two todo items: "Todo 1" and "Todo 2"
- Marks "Todo 1" as complete by clicking its checkbox
- Finds and clicks the "Active" filter button
- Checks if "Todo 1" (completed) is no longer visible
- Checks if "Todo 2" (active) is still visible
test('filters todos', () => {
render(<TodoList />);
const input = screen.getByPlaceholderText('Add a new todo');
const addButton = screen.getByText('Add');
fireEvent.change(input, { target: { value: 'Todo 1' } });
fireEvent.click(addButton);
fireEvent.change(input, { target: { value: 'Todo 2' } });
fireEvent.click(addButton);
const firstCheckbox = screen.getAllByRole('checkbox')[0];
fireEvent.click(firstCheckbox);
const activeFilterButton = screen.getByText('Active');
fireEvent.click(activeFilterButton);
expect(screen.queryByText('Todo 1')).not.toBeInTheDocument();
expect(screen.getByText('Todo 2')).toBeInTheDocument();
});
These tests cover the main functionality of the TodoList component:
- Adding new todos
- Marking todos as complete
- Deleting todos
- Filtering todos based on their status (active/completed)
\
we can also have multiple test files for each separate component inside TodoList
.
AddTodo
Create a AddTodos.test.js
under src/components/__test__
for testing the addtodo component
Form Submit
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import AddTodo from './../AddTodo.js';
test('calls addTodo prop with input value when form is submitted', () => {
const mockAddTodo = jest.fn();
render(<AddTodo addTodo={mockAddTodo} />);
const input = screen.getByPlaceholderText('Add a new todo');
const button = screen.getByText('Add');
fireEvent.change(input, { target: { value: 'New todo' } });
fireEvent.click(button);
expect(mockAddTodo).toHaveBeenCalledWith('New todo');
});
Input Clear After Submission
test('clears input after form submission', () => {
render(<AddTodo addTodo={() => { }} />);
const input = screen.getByPlaceholderText('Add a new todo');
const button = screen.getByText('Add');
fireEvent.change(input, { target: { value: 'New todo' } });
fireEvent.click(button);
expect(input.value).toBe('');
});
FilterTodo
Create a FilterTodos.test.js
under src/components/__test__
import React from 'react';
import { render, screen, fireEvent } from '@testing-library/react';
import FilterTodos from './../FilterTodos.js';
test('calls setFilter with correct argument when buttons are clicked', () => {
const mockSetFilter = jest.fn();
render(<FilterTodos setFilter={mockSetFilter} />);
fireEvent.click(screen.getByText('All'));
expect(mockSetFilter).toHaveBeenCalledWith('all');
fireEvent.click(screen.getByText('Active'));
expect(mockSetFilter).toHaveBeenCalledWith('active');
fireEvent.click(screen.getByText('Completed'));
expect(mockSetFilter).toHaveBeenCalledWith('completed');
});
\
To run the test cases we've created, just run npm test
in the terminal.
Oops! Something went wrong as one of the testcase failed under TodoList.test.js
Debugging and Finding Errors Using Test Cases
One of the main benefits of writing test cases is that they help us catch errors early in the development process. When a testcase fails, it provides valuable insights about where and why the failure occurred, allowing you to quickly diagnose and fix the problem.
\
There is a subtle bug into the
AddTodo
component that will cause one of the test cases to fail.
Fixing Error
In the handleSubmit
function, the line that clears the input field (setText('')
) after submitting the form has been commented out. This will cause the input field to retain the text even after the form is submitted.
\
modify AddTodo.js
file under components
import React, { useState } from 'react';
function AddTodo({ addTodo }) {
const [text, setText] = useState('');
const handleSubmit = (e) => {
e.preventDefault();
if (!text.trim()) return;
addTodo(text);
setText('');
};
return (
<form onSubmit={handleSubmit}>
<input
type="text"
value={text}
onChange={(e) => setText(e.target.value)}
placeholder="Add a new todo"
/>
<button type="submit">Add</button>
</form>
);
}
export default AddTodo;
Running the Tests
Hurray 🎉! Our tests are running fine.
Conclusion
In this article, we explore React testing using Jest and React Testing Library. We introduce the importance of testing in React development, set up a React testing environment, and explain essential Jest concepts and syntax.
Testing isn't just a checkbox on a to-do list; it's a crucial aspect of modern software development. By adopting a thorough testing strategy with tools like Jest and React Testing Library, we’re setting the foundation for a more robust, reliable, and maintainable React application.
FAQs
1. What is the difference between Jest and React Testing Library?
Jest is a JavaScript testing framework designed for unit testing, while React Testing Library is a utility to test React components by simulating user interactions. Jest provides testing infrastructure, including a test runner, assertion library, and mocking capabilities, whereas React Testing Library focuses on testing the rendered output of components to ensure they work as intended.
2. How do I mock API calls in Jest when testing React components?
You can mock API calls in Jest by using its built-in jest.mock
function. This allows you to mock modules or functions, such as API calls, and simulate different responses during your tests. You can then assert that the component behaves as expected based on these mocked responses.
3. What is the best approach to test asynchronous code in React components?
Jest supports asynchronous testing using async/await syntax. To test asynchronous code in React components, you should use async
functions in your tests and await
on functions that return promises. You can also use helper methods like waitFor
from React Testing Library to wait for updates in the DOM after an asynchronous operation.
4. How do I simulate user events like clicks or input changes in React Testing Library?
React Testing Library provides the fireEvent
and userEvent
utilities to simulate user interactions such as clicks, typing, and form submissions. These tools let you mimic real user actions in your tests and verify how the component responds to them.
5. Why is it important to use screen
for querying elements in React Testing Library?
screen
is a utility provided by React Testing Library that allows you to access all elements in the rendered DOM. It simplifies queries by making them more readable and reducing the need to destructure multiple functions from render
. Using screen
ensures your tests are more intuitive and maintain
This content originally appeared on HackerNoon and was authored by keploy
keploy | Sciencx (2024-08-20T22:22:40+00:00) How to Test React Components With Jest And React Testing Library. Retrieved from https://www.scien.cx/2024/08/20/how-to-test-react-components-with-jest-and-react-testing-library/
Please log in to upload a file.
There are no updates yet.
Click the Upload button above to add an update.