I’m Sure You Didn’t Know You Could Test React Components This Easily

Using React Testing Library To Test React Components

Nishu_Dissanayake
Bits and Pieces

--

Introduction

When it comes to React applications, testing is not just a best practice but an essential part of the whole development process to assure the overall quality and facilitate long-term maintenance of the application. Especially when complex applications are concerned, it becomes tedious to manually test every component behavior as the application grows.

Therefore, automated tests tend to act as a safety net to ensure that the components behave as expected when new changes are introduced to the system.

Among the testing frameworks currently being used to test React components, Jest, React Testing Library, and Enzyme hold special places.

Out of these, the React Testing Library is a solution currently gaining continuous momentum that you cannot afford to miss, given its many features and benefits to the test teams. These tools help improve the quality of the code and make it more manageable in the long run.

What is the React Testing Library?

React Testing Library is not your average testing framework by any means. It is a perfect choice for modern development workflows and is a very lightweight solution that works with actual DOM nodes rather than with instances of rendered React components. This can be used as a replacement for Enzyme but not for Jest, and most of the time, it is used together with Jest.

Yet, it is not a framework or a test runner and is not specific to one testing framework like Jest but works with any testing framework.

Unlike many other testing frameworks out there, the React Testing library focuses explicitly on how the user would interact with the components, prioritizing getting the tests as close as possible to this user behavior.

Therefore, it is a fantastic solution that can give one much confidence that the application would work as expected when a real user uses it. This user-centric testing ensures that you are not just testing code but are actively testing the user's experience of interacting with your React application. Thus, the React Testing Library stands out as a solution that pays more attention to testing user interactions rather than implementation details.

React component testing doesn’t have to be hard.

If you’re new to building React apps, think of components as an independent space that should be developed and maintained in isolated spaces and not in your traditional monolith spaces. This lets you better maintain your components in the future.

And, one advantage in doing so is using independent test environments to ensuring that your tests remain isolated and fast as well.

A React Component Tree

Consider React components like the figure shown above, They’re coming from two different spaces, and is versioned differently. But you have to capability in isolating your tests with tools like Bit and also ensure that one change in your component tests all of its usages through modern frontend CI technologies like Ripple CI.

Getting Started with React Testing Library

But, for now, let’s see how we can use the React Testing Library to test React components.

Setting up and getting started with React Testing Library is relatively straightforward.

Step 1 - Installation and Configuration of React Testing Library.

With npm:

npm install --save-dev @testing-library/react

With yarn:

yarn add --dev @testing-library/react

For a more comprehensive setup with additional utilities:

npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event

React Testing Library usually does not need extra configurations and works seamlessly with React applications.

But if your application is bound to become complex or you feel the need to write more complex tests, you can try using Jest's configuration capabilities to set up an environment that works best in your case. For instance, as shown below, you can centralize the global testing setup by creating a central place for the global test configuration.

Next, create the files below at the root of your project directory.

testSetup.js

// src/testSetup.js
// You can use this to import additional Jest matchers, making it easier to write clean, readable tests
import '@testing-library/jest-dom';

jest.config.js

// jest.config.js
module.exports = {
setupFilesAfterEnv: ['<rootDir>/src/testSetup.js'],
};

Step 2 - Creating component files.

As a first attempt, you can try getting started with testing by creating a few simple components.

Button:

// src/components/Button.js
import React from 'react';

export function Button({ onClick, label }) {
return <button onClick={onClick}>{label}</button>;
}

Step 3 - Creating test files.

As for the test files, it is essential to follow a clear and concise approach, and you need to have different test files for each corresponding component.

The test file below simulates a button click event and test whether it triggers the expected function using the query getByText().

// src/components/Button.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import { Button } from './Button';

it('Button responds to click event', () => {
const handleClick = jest.fn();
render(<Button onClick={handleClick} label="Click me!" />);

fireEvent.click(screen.getByText(/click me!/i));
expect(handleClick).toHaveBeenCalledTimes(1);
});

Your folder structure will mirror the below format when properly organized.

├── src/
├── components/
├── Button.js
├── Button.test.js
├── ToggleButton.js
├── ToggleButton.test.js

Writing Component Tests with React Testing Library

Step 4 - Testing Components with States and Props.

Apart from the example above, the React Testing Library offers quite a wide variety of query selectors and user events that allow you to interact with the DOM a a real user would. The example below shows an example of writing tests for components that rely on states or props to determine their behavior.

ToggleButton.js

// src/components/ToggleButton.js
import React, { useState } from 'react';

export function ToggleButton() {
const [isOn, setIsOn] = useState(false);

return (
<button onClick={() => setIsOn(!isOn)}>
{isOn ? 'On' : 'Off'}
</button>
);
}

ToggleButton.test.js

// src/components/ToggleButton.test.js
import { render, screen, fireEvent } from '@testing-library/react';
import { ToggleButton } from './ToggleButton';

it('Toggle button switches state on click', () => {
render(<ToggleButton />);
const button = screen.getByRole('button');
expect(button).toHaveTextContent(/off/i);
fireEvent.click(button);
expect(button).toHaveTextContent(/on/i);
});

This test involves testing for button text content changes on click, reflecting the component's state change.

Step 5 - Testing Asynchronous Code.

You must pay special attention when it comes to writing test suites for components that use promises or interact with APIs. You can try out the below example that tests a component that fetches data.

FetchComponent.js

// src/components/FetchComponent.js
import React, { useState, useEffect } from 'react';

export function FetchComponent({ url }) {
const [data, setData] = useState(null);

useEffect(() => {
async function fetchData() {
const response = await fetch(url);
const result = await response.json();
setData(result);
}

fetchData();
}, [url]);

if (!data) return <div>Loading...</div>;
return <h1>{data.greeting}</h1>;
}

FetchComponent.test.js

// src/components/FetchComponent.test.js
import { render, screen, waitFor } from '@testing-library/react';
import { FetchComponent } from './FetchComponent';

it('FetchComponent displays data after async fetch', async () => {
global.fetch = jest.fn().mockImplementation(() =>
Promise.resolve({
json: () => Promise.resolve({ greeting: 'Hello!' }),
})
);

render(<FetchComponent url="/greeting" />);

await waitFor(() => expect(screen.getByRole('heading')).toHaveTextContent('Hello!'));
});

Apart from these, it is also possible to mock external API calls inside the component's tests.

Step 6 - Running the Tests.

Executing the tests that you have written so far is simple. Once you use the below command, the tests will run and display the final result on your terminal.

npm test

You have to ensure that all tests pass to verify that your components are functioning properly with the intended behavior.

Step 7 - Test Coverage and Code Quality.

You can use metrics such as test coverage to determine how much of the entire codebase the tests cover. A high test coverage is a good sign that indicates your application is less prone to bugs. But instead of focusing on 100% test coverage, writing meaningful tests to achieve a higher code quality is important.

When it comes to testing, it is not just about hitting the numbers but is about testing the right things.

npm test -- --coverage

You can find the complete example in the GitHub repository linked here.

But wait, here's one more thing: speed test and performance testing in React

It is of utmost importance to perform speed and performance testing to ensure that your users get a smoothly running application for optimal user experience.

One major issue in React apps is that they tend to slow down when you have complex state management and rendering behavior. Therefore, it's important that you optimize these aspects of your app before a production release.

So, use React performance testing tools like the react-performance-testing library and React Profiler API to determine the performance and speed of the application.

Best Practices for React Testing with React Testing Library

Alright, once you've gotten started with the React Testing Library, it's important to ensure that you follow certain practises that are well acknowledged in the industry to ensure that your test cases are up to par.

So, do ensure that you consider these practises when you're working with React Testing Library:

  • Clarity is crucial. Write descriptive and clear test cases, and ensure to be descriptive on the test case when you're defining it.
  • Avoid testing implementation details and focus on user-facing features.
  • Test one thing at a time and keep your tests isolated from the external state or outcome of other tests. Don't test two aspects of your component in one unit test.
  • When elements don't have natural selectors, use data-testid attributes for a stable handle.
  • If you're relying on API data for a component, don't invoke your APIs in your tests, but rather mock the data using Mocks.

Conclusion

React Testing Library is a powerful ally that can help you achieve higher quality and performance in your React application.

These tools have proven their value in the production environments and are bound to add more value to your applications.

Therefore, embracing them will ensure your React application's reliability and refine the development process.

If you wish to browse through the app we tested, feel free to explore my GitHub repository.

Happy testing!

--

--