Testing a React component will most often involve some type of user interaction happening before you validate the expected outcome. This could include a user typing into a form field, pressing the escape key to close a modal, or clicking on an element. This guide will show you how to use these types of events in your tests.
An essential tool for writing tests for React components is the React Testing Library. The React Testing Library (RTL) was created to help you write maintainable tests that focus on how your software is being used, instead of implementation details. Go here to learn more about RTL. The examples in this guide will use RTL.
When your React component involves a user clicking on an element, you'll want to use the fireEvent
method from RTL. To illustrate this behavior, here is a simple component with a button that, when clicked, toggles the view of an image:
1import React from 'react';
2export default function App() {
3 const [isVisible, setIsVisible] = React.useState(false);
4 const handleClick = () => {
5 setIsVisible(!isVisible);
6 };
7
8 return (
9 <div className='App'>
10 <div>
11 <button onClick={handleClick}>Toggle Image</button>
12 </div>
13
14 <br />
15
16 <div style={{ display: isVisible ? 'block' : 'none' }}>
17 <p>Look at this pretty cat</p>
18 <img src='https://cataas.com/cat' alt='random cat' />
19 </div>
20 </div>
21 );
22}
When this component first renders, the cat image will not be displayed. You can show and hide the image by clicking a Toggle Image
button. To test the toggle functionality you would use the fireEvent.click()
method:
fireEvent
method from RTLjest-dom
from RTL to use assertions like toBeVisible()
fireEvent.click(element)
method to click an element1import React from 'react';
2// 1
3import { render, screen, fireEvent } from '@testing-library/react';
4
5// 2
6import '@testing-library/jest-dom';
7import App from './App';
8
9describe('<App />', () => {
10 it('Should toggle the cat image div', () => {
11 render(<App />);
12 // Before clicking the toggle button, the image is NOT visible
13 expect(screen.queryByText(/look at this pretty cat/i)).not.toBeVisible();
14
15 // 3
16 fireEvent.click(screen.queryByText(/toggle image/i));
17 expect(screen.queryByText(/look at this pretty cat/i)).toBeVisible();
18 });
19});
When you need to test a component that involves user input, you should use the user-event
methods available in the @testing-library/user-event
package. user-event is used to simulate real events that would happen in the browser like typing, clicking, or interacting with a select menu. Here's an example component that has the user type in an email address to see if it's valid:
1import React from 'react';
2export default function Email() {
3 const [emailAddress, setEmail] = React.useState();
4 const [hasValidEmail, setHasValidEmail] = React.useState(false);
5 const handleChange = (event) => {
6 setEmail(event.target.value);
7 };
8
9 const handleClick = () => {
10 const regex = /^[a-zA-Z0-9._-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,4}$/;
11 setHasValidEmail(regex.test(emailAddress));
12 };
13
14 return (
15 <div className='App'>
16 <div>
17 <label htmlFor='email'>Email</label>
18 <input
19 type='text'
20 id='email'
21 name='email'
22 onChange={handleChange}
23 value={emailAddress}
24 />
25 <button onClick={handleClick}>Check if email is valid</button>
26 </div>
27 {hasValidEmail && emailAddress ? (
28 <p>Email Address is valid</p>
29 ) : (
30 <p>Email Address is NOT valid</p>
31 )}
32 </div>
33 );
34}
To test this component you'll use the type method userEvent.type()
, where you'll pass in the input element as the first argument and the value as the second argument:
userEvent.type()
to enter an invalid email addressclear
method to clear out the value of an input element1import React from 'react';
2import { render, screen, fireEvent } from '@testing-library/react';
3import userEvent from '@testing-library/user-event';
4import Email from './Email';
5import '@testing-library/jest-dom';
6
7describe('<Email />', () => {
8 it('Should validate the email address typed by the user', () => {
9 render(<Email />);
10
11 // 1
12 userEvent.type(screen.queryByLabelText(/email/i), 'ham');
13 fireEvent.click(
14 screen.queryByRole('button', { name: /Check if email is valid/i }),
15 );
16 expect(screen.queryByText(/email address is not valid/i)).toBeVisible();
17
18 // 2
19 userEvent.clear(screen.queryByLabelText(/email/i));
20
21 // 3
22 userEvent.type(screen.queryByLabelText(/email/i), '[email protected]');
23 fireEvent.click(
24 screen.queryByRole('button', { name: /check if email is valid/i }),
25 );
26 expect(screen.queryByText(/email address is not valid/i)).toBeNull();
27 expect(screen.queryByText(/email address is valid/i)).toBeVisible();
28 });
29});