4

Replacing Enzyme with React Testing Library

 2 years ago
source link: https://wanago.io/2022/04/04/enzyme-react-testing-library/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client
Replacing Enzyme with React Testing Library

JavaScript React

April 4, 2022

A few days ago, React released version 18, which is not compatible with Enzyme. Furthermore, it probably is not achievable to use Enzyme with React 18. If you’re still using Enzyme, it is time to look into alternatives. The most popular option besides Enzyme seems to be React Testing Library. However, incorporating RTL requires a slightly different testing approach and is not a drop-in replacement for Enzyme. This article goes through some of the most crucial aspects of testing and compares Enzyme and React Testing Library.

Testing an example application with both approaches

The React Testing Library aims to provide a lightweight solution for testing React components. Its most fundamental principle is to do testing in a way that resembles how our component would be used in a real application. RTL gives us the tool to interact with the component in a way that the user would.

Defining a simple counter application

To better grasp the idea, let’s create a very straightforward application. Our Counter component displays a button and a label.

Counter.tsx
import React from 'react';
import useCounter from './useCounter';
import CounterLabel from './CounterLabel';
const Counter = () => {
  const { counterNumber, handleButtonClick } = useCounter();
  return (
    <div>
      <button onClick={handleButtonClick}>Click</button>
      <CounterLabel counterNumber={counterNumber} />
    </div>
export default Counter;
CounterLabel.tsx
import React, { FunctionComponent } from 'react';
interface Props {
  counterNumber: number;
const CounterLabel: FunctionComponent<Props> = ({ counterNumber }) => {
  return (
    <p>The number of clicks: {counterNumber}</p>
export default CounterLabel;

When we click on the button, the label should display a bigger number.

useCounter.tsx
import { useState } from 'react';
function useCounter() {
  const [counterNumber, setCounterNumber] = useState(0);
  const handleButtonClick = () => {
    setCounterNumber(counterNumber + 1);
  return {
    counterNumber,
    handleButtonClick,
export default useCounter;

Testing with React Testing Library

Let’s test if the Counter component displays the correct number before and after clicking a button.

Counter.test.tsx
import { render, fireEvent } from '@testing-library/react';
import Counter from './Counter';
describe('The Counter component', () => {
  describe('if the button is not clicked', () => {
    it('should display 0', () => {
      const counter = render(<Counter />);
      const paragraph = counter.getByText('The number of clicks: 0', {
        selector: 'p',
      expect(paragraph).toBeDefined();
  describe('if the button clicked once', () => {
    it('should display 1', () => {
      const counter = render(<Counter />);
      const button = counter.getByText('Click', { selector: 'button' });
      fireEvent.click(button);
      const paragraph = counter.getByText('The number of clicks: 1', {
        selector: 'p',
      expect(paragraph).toBeDefined();

PASS src/Counter/Counter.test.tsx
The Counter component
if the button is not clicked
✓ should display 0
if the button clicked once
✓ should display 1

In the above test, we check the simulated DOM to see if React rendered the correct number before and after we’ve clicked the button.

Testing with Enzyme

We can use a different testing approach if we want to test the above component with Enzyme.

Counter.test.tsx
import { shallow } from 'enzyme';
import Counter from './Counter';
import CounterLabel from './CounterLabel';
describe('The Counter component', () => {
  describe('if the button is not clicked', () => {
    it('should render CounterLabel with 0', () => {
      const counter = shallow(<Counter />);
      const counterLabel = counter.find(CounterLabel);
      expect(counterLabel.prop('counterNumber')).toBe(0);
  describe('if the button clicked once', () => {
    it('should render CounterLabel with 1', () => {
      const counter = shallow(<Counter />);
      const increaseCounterNumber = button.prop('onClick');
      increaseCounterNumber();
      const counterLabel = counter.find(CounterLabel);
      expect(counterLabel.prop('counterNumber')).toBe(1);

PASS src/Counter/Counter.test.tsx
The Counter component
if the button is not clicked
✓ should render CounterLabel with 0
if the button clicked once
✓ should render CounterLabel with 1

We don’t check the simulated DOM in the above test to see what number React rendered. Instead, we check if the Counter component rendered the CounterLabel with the right prop.

Comparing the React Testing Library and Enzyme

In the above test, React Testing Library forces us to write tests that resemble the way that a real user acts. In contrast, Enzyme allows us to test the implementation details of a component more deeply.

Integration tests and unit tests

We can perceive the test we wrote with Enzyme as a unit test. We only test the Counter component and don’t care about the CounterLabel. Even if the CounterLabel stops displaying the label, our test won’t fail.

We can call the test we wrote with React Testing Library an integration test. We verify if the Counter component works well with the CounterLabel to display the right message to the user. If the CounterLabel stops working correctly, the test we wrote with RTL will fail.

Both of the above approaches have their consequences. So let’s modify our component a little and see if the tests still pass.

CounterLabel.tsx
import React, { FunctionComponent } from 'react';
interface Props {
  counterNumber: number;
const CounterLabel: FunctionComponent<Props> = ({ counterNumber }) => {
  return <p>The number of clicks: {counterNumber * 100}</p>;
export default CounterLabel;

Our CounterLabel component no longer displays the counter correctly. Because of that, the test we wrote with React Testing Library does not pass anymore. Unfortunately, the test we wrote in Enzyme did not fail. Inside, we only check if the Counter component passes the counterNumber correctly to the CounterLabel.

Let’s modify our component differently, not necessarily causing a bug.

Counter.tsx
import React from 'react';
import useCounter from './useCounter';
const Counter = () => {
  const { counterNumber, handleButtonClick } = useCounter();
  return (
    <div>
      <button onClick={handleButtonClick}>Click</button>
      <p>The number of clicks: {counterNumber}</p>;
    </div>
export default Counter;

Above, we’re not using the CounterLabel component anymore. Instead, we render the counterNumber directly in the Counter component.

From the user’s point of view, nothing changed. Unfortunately, the tests we wrote using Enzyme fail now. There, we wrote the following line of code:

const counterLabel = counter.find(CounterLabel);

Since we no longer use the CounterLabel component, the tests written with Enzyme don’t pass. However, the tests we wrote with React Testing Library still passed without issues. They don’t care whether or not we use the CounterLabel component.

Shallow and full rendering

In our Enzyme test, we use shallow rendering. It does not render the child components. Therefore, it emphasizes unit testing.

It’s worth noticing that we don’t have to use shallow rendering with Enzyme. Instead, we can use the mount function. It will render both Counter and CounterLabel, allowing us to write an integration test.

Counter..test.tsx
import { mount } from 'enzyme';
import Counter from './Counter';
describe('The Counter component', () => {
  describe('if the button is not clicked', () => {
    it('should display 0', () => {
      const counter = mount(<Counter />);
      const paragraph = counter.find('p');
      expect(paragraph.text()).toBe('The number of clicks: 0');
  describe('if the button clicked once', () => {
    it('should display 1', () => {
      const counter = mount(<Counter />);
      const button = counter.find('button');
      button.simulate('click');
      const paragraph = counter.find('p');
      expect(paragraph.text()).toBe('The number of clicks: 1');

The above test we wrote with Enzyme behaves similarly to the test we previously wrote with React Testing Library. However, it does not care much about the implementation details of the Counter component. Therefore, it allows us to modify it to some extent without the test failing.

In React Testing Library, the shallow rendering is not available. Although we could mock the components we don’t want to render, the official FAQ discourages that.

Summary

At some point, React Testing Library became more widely used than Enzyme. Since Enzyme can’t work with React 18, which was recently released, it will probably cause RTL to surpass Enzyme even more.

Screenshot-from-2022-04-03-22-01-07.png

In this article, we’ve gone through their most apparent differences and how it changes how we write tests. The crucial thing is that the React Testing Library is not a straightforward replacement for Enzyme. Both React Testing Library and Enzyme are good libraries, and they share a set of functionalities.

Enzyme gives us more flexibility in the way that we write tests. React Testing Library, on the other hand, is more opinionated. It forces us to have a specific mindset and focus on what our user experiences. Instead of testing the implementation details, we have to simulate how a user would interact with our components. It might force us to shift our approach if we want to switch to React Testing Library. We might end up with tests that follow more best practices. It could also improve our confidence in our code and make sure that the application works well as a whole.


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK