Seamless Digital Experience.
Happy Customers.

Digital Experience and Error Monitoring Platform - Zipy

Migrating from Enzyme to React Testing Library

Adarsh Gupta
~ 15 min read | Published on Jun 05, 2024





TABLE OF CONTENT

Fix bugs faster with Zipy!

  • Session replay
  • Network calls
  • Console Logs
  • Stack traces
  • User identification
Get Started for Free

As software engineers, we often find ourselves in circumstances where we are unable to distinguish between different possibilities and decide which library is the best fit for our purposes.

Developers using React JS have access to a number of testing libraries and tools for component-level testing, including as Enzyme, Jest, and the Testing Library. Modern web development requires component testing, particularly for large apps developed with React.

This detailed blog on migrating from Enzyme to React Testing Library will explain how to migrate your test automation suite to React Testing Library and understand the pros and cons of doing so. If you want to see a comparision between React-testing-library vs Enzyme then read here.

What exactly is Component Testing and why do we need it?

It is well known that React is a component-based user interface library that comprises two kinds of components: functional and class components. Class components are similar to JavaScript classes, and functional components are similar to JavaScript functions. Prior to learning React, one must have a fundamental understanding of JavaScript fundamentals.

The functional component is most likely what you will use, and core React developers encourage you to design functional components as well. Your application's layout logic will incorporate each element, and when combined, they complete the whole.

You can perform several types of tests, such as component testing and unit testing, if your component is merely a basic user interface. This way, you won't need to complete as many functionality checks. To make sure that more complicated components are functioning as intended, it's crucial to carry out exhaustive unit testing in React if they have states or depend on particular behaviors.

The kind of testing known as component testing focuses on individual code units, or components in this context. The primary goal of component testing is to make sure that every component functions as intended, whether it be a class or functional component.

The majority of online apps employ some kind of input form to get data from users; therefore, it's critical to make sure the implementation is operating as planned to avoid losing out on potential clients.

By ensuring that every function you have created is operating accurately and consistently, component testing improves user experience.

Official Documentation of React

While Enzyme and React Testing Library are both options for component testing in React, React Testing Library is often considered a better choice for several reasons, and even the official React team recommends using React Testing Library.

What is Enzyme?

Enzyme is an open-source JavaScript library that makes React testing easier. It has over 2 million weekly downloads on npmjs.com 

npm stats

Enzyme allows you to browse, manipulate, and simulate runtime in certain ways based on the output. Enzyme's DOM manipulation and traversal API is designed to be as flexible and user-friendly as jQuery's.

In the past three years, Enzyme has only released one release, and it doesn't seem like they want to keep working on it. Since the world of development is always evolving, as developers we are aware that Enzyme is unable to provide the finest testing features for contemporary web development, and this situation does not appear to be improving anytime soon.

Despite the lack of an official Enzyme adapter for React 17, its developer produced a temporary adapter that, with 16 million downloads, has become the de facto default adapter. But all this has done is postpone Enzyme's ultimate demise and provide developers with a fictitious sense of security.

The project's author, Wojciech Maj, made a formal announcement at the end of 2021 stating that he had ceased working on it and that there would be no more updates.

Offical blog of Wojciech Maj

He also mentioned using the React Testing Library, which is a fairly new but officially accepted testing library for React.

What is React Testing Library

React Testing Library is another open-source JavaScript library that helps with react testing, and it has over 7 million weekly downloads on npmjs.com. It has around 17.5K stars on Github, and around 150+ developers are currently working on the project.

npm stats

According to the state of JavaScript survey 2022, React Testing Library ranks 2nd in the retention metric and is steadily climbing the ladder.

State of JS survey 2022

The react-testing-library package family can help you test your UI components in a way that prioritizes the user experience for your website or app. The library’s guiding principle is “The more your tests resemble the way your software is used, the more confidence they can give you."

The main problem React developers face when writing tests is wanting to write maintainable tests that give them high confidence that their components are working for their users. That is, with the React testing library, developers can concentrate on the functionality of the component rather than the actual implementation.

Making the Switch from Enzyme to React Testing Library: A Step-by-Step Guide

To smoothly switch from Enzyme to React Testing Library, it's best to do it step by step. This means using both libraries at the same time in your application and gradually converting your Enzyme tests to React Testing Library tests one by one. This approach lets you migrate even large and complex applications without disrupting other businesses, as the work can be done collaboratively and over time.

Practical example for migrating to React Testing Library

Migrating from Enzyme to React Testing Library (RTL) can be a straightforward process if you follow a few best practices. But before diving into these practices, let's see what an actual migration of code looks like by comparing Enzyme and React Testing Library

Here we have a React JS component with four text input elements, and throughout this blog, we will use this example.

import React, { useState } from "react";
import "./styles.css";

const Form = () => {
  const [firstName, setFirstName] = useState("");
  const [lastName, setLastName] = useState("");
  const [email, setEmail] = useState("");
  const [password, setPassword] = useState("");
  const [errors, setErrors] = useState({});

  const handleSubmit = (e) => {
    e.preventDefault();

    const errors = {};

    if (!firstName) {
      errors.firstName = "First name is required";
    }

    if (!lastName) {
      errors.lastName = "Last name is required";
    }

    if (!email) {
      errors.email = "Email is required";
    } else if (!/\S+@\S+\.\S+/.test(email)) {
      errors.email = "Email is invalid";
    }

    if (!password) {
      errors.password = "Password is required";
    } else if (password.length < 8) {
      errors.password = "Password must be at least 8 characters long";
    }

    setErrors(errors);

    if (Object.keys(errors).length === 0) {
      console.log("Submitting form...");
    }
  };

  return (
    <form onSubmit={handleSubmit}>
      <div>
        <label htmlFor="firstName">First Name:</label>
        <input
          type="text"
          id="firstName"
          value={firstName}
          onChange={(e) => setFirstName(e.target.value)}
        />
        {errors.firstName && (
          <div style={{ color: "red" }}>{errors.firstName}</div>
        )}
      </div>
      <div>
        <label htmlFor="lastName">Last Name:</label>
        <input
          type="text"
          id="lastName"
          value={lastName}
          onChange={(e) => setLastName(e.target.value)}
        />
        {errors.lastName && (
          <div style={{ color: "red" }}>{errors.lastName}</div>
        )}
      </div>
      <div>
        <label htmlFor="email">Email:</label>
        <input
          type="email"
          id="email"
          value={email}
          onChange={(e) => setEmail(e.target.value)}
        />
        {errors.email && <div style={{ color: "red" }}>{errors.email}</div>}
      </div>
      <div>
        <label htmlFor="password">Password:</label>
        <input
          type="password"
          id="password"
          value={password}
          onChange={(e) => setPassword(e.target.value)}
        />
        {errors.password && (
          <div style={{ color: "red" }}>{errors.password}</div>
        )}
      </div>
      <button type="submit">Submit</button>
   </form>
  );
};

export default Form;

The output will be like this

output preview after migrating to react testing library

If you want to see the full code here is the source code 

For this example, we will have three test cases here:

  1. The first test case checks if the Form component renders a form with four input fields and a submit button.
  2. The second test case checks if the state of the Form component is updated when the input fields change.
  3. The third test case checks if the onSubmit function is called with the form data when the submit button is clicked.

Here is an example of how the above test cases could be written using Enzyme:

import React from "react";
import { shallow } from "enzyme";
import Form from "./Form";

describe("Form", () => {
  it("should render a form with four input fields and a submit button", () => {
    const wrapper = shallow(<Form />);
    expect(wrapper.find("form")).toHaveLength(1);
    expect(wrapper.find("label")).toHaveLength(4);
    expect(wrapper.find("input")).toHaveLength(4);
    expect(wrapper.find("button")).toHaveLength(1);
  });

  it("should update the state when input fields change", () => {
    const wrapper = shallow(<Form />);
    const nameInput = wrapper.find('input[name="name"]');
    const emailInput = wrapper.find('input[name="email"]');

    nameInput.simulate("change", { target: { name: "name", value: "John" } });
    emailInput.simulate("change", {
      target: { name: "email", value: "john@example.com" },
    });

    expect(wrapper.state("name")).toEqual("John");
    expect(wrapper.state("email")).toEqual("john@example.com");
  });

  it("should call the onSubmit function with the form data when submitted", () => {
    const mockOnSubmit = jest.fn();
    const wrapper = shallow(<Form onSubmit={mockOnSubmit} />);
    const nameInput = wrapper.find('input[name="name"]');
    const emailInput = wrapper.find('input[name="email"]');
    const submitButton = wrapper.find("button");

    nameInput.simulate("change", { target: { name: "name", value: "John" } });
    emailInput.simulate("change", {
      target: { name: "email", value: "john@example.com" },
    });
    submitButton.simulate("submit", { preventDefault: jest.fn() });

    expect(mockOnSubmit).toHaveBeenCalledWith({
      name: "John",
      email: "john@example.com",
    });
  });
});

Let's now convert it to the React Testing Library:

import React from "react";
import { render, screen, fireEvent } from "@testing-library/react";
import Form from "./Form";

describe("Form", () => {
  it("should render a form with two input fields and a submit button", () => {
    render(<Form />);
    expect(screen.getByRole("form")).toBeInTheDocument();
    expect(screen.getAllByRole("textbox")).toHaveLength(2);
    expect(screen.getByRole("button", { name: /submit/i })).toBeInTheDocument();
  });

In the React Testing Library, we use the render method to render the Form element and use screen queries to check if the rendered form contains the required items.

The second test case will be updated with the following snippet of code:
  it("should update the state when input fields change", () => {
    render(&lt;Form /&gt;);
    const nameInput = screen.getByLabelText("Name");
    const emailInput = screen.getByLabelText("Email");

    fireEvent.change(nameInput, { target: { name: "name", value: "John" } });
    fireEvent.change(emailInput, { target: { name: "email", value: "john@example.com" } });

    expect(nameInput).toHaveValue("John");
    expect(emailInput).toHaveValue("john@example.com");
  });

This second test case again uses the render method to load the component. It then uses the screen object to get the input fields by their label text using the getByLabelText query.

The test then uses the fireEvent.change function to simulate a change event on each input field. This updates the state of the component with the new values. Finally, it uses the toHaveValue matcher from the @testing-library/jest-dom package to check if the values of the input fields have been updated correctly.

The third test case will be updated with the following snippet of code:

it("should call the onSubmit function with the form data when submitted", () => {
    const mockOnSubmit = jest.fn();
    render(&lt;Form onSubmit={mockOnSubmit} /&gt;);
    const nameInput = screen.getByLabelText("Name");
    const emailInput = screen.getByLabelText("Email");
    const submitButton = screen.getByRole("button", { name: /submit/i });

    fireEvent.change(nameInput, { target: { name: "name", value: "John" } });
    fireEvent.change(emailInput, { target: { name: "email", value: "john@example.com" } });
    fireEvent.click(submitButton);

    expect(mockOnSubmit).toHaveBeenCalledWith({
      name: "John",
      email: "john@example.com",
    });
  });
});

This test case determines whether clicking the submit button causes the onSubmit function to be triggered with the form data. It makes a mock function called mockOnSubmit and gives it to the Form component as a prop.

After that, it uses the screen object from @testing-library/react to retrieve the input fields and submit button, fireEvent from the same library is used to mimic changes to the input fields and a click event on the button, and it declares that mockOnSubmit was called with the expected data.

Upon reviewing the code, it is evident that React Testing Library does not offer direct access to the props or component state, in contrast to Enzyme. Rather, it motivates you to test your component by engaging with the rendered result and asserting what should be displayed in a user-centered manner.

The variations discovered in our test suite are as follows:

  • Usually, you would use the render method from @testing-library/react instead of the shallow or mount methods from Enzyme.
  • To search for items, you would usually use one of the DOM query methods from @testing-library/dom, like getByLabelText, getByRole, or getByText, rather than Enzyme's find function.
  • Usually, to interact with a component, you would simulate user interactions using the fireEvent utility from @testing-library/react, rather than directly accessing the component's state or properties.
  • Typically, you would use the prettyDOM function from @testing-library/dom to obtain a string representation of the rendered output of the component rather than Enzyme's debug method for logging the component's output.

Remember that this is only a broad perspective, and you may wish to add more test cases as you work on code conversion, based on your needs.

The render method of the React Testing Library renders the component and all of its sub-components in a manner identical to that of Enzyme's mount method, in contrast to Enzyme's shallow renderer, as can be seen in this example.

It is not always necessary to assign the render result to a variable in the React Testing Library, meaning that a wrapper is not always required. Calling functions on the screen object will allow you to easily access the rendered output.

Another useful feature of the React Testing Library is that cleanup is automatically called in an afterEach or beforeEach function, so you don't need to do it after every test.

React Testing Library is one of those tools that people fall in love with right away since it makes creating more accessible applications easier (because inaccessible apps are more difficult to test).

However, even Enzyme's developer suggests starting again from scratch. Because React Testing Library is an alternative to Enzyme rather than a drop-in replacement for it.

Install React Testing Library

You can install the React Testing Library using the following command, you can use npm or yarn or pnpm, whichever one you prefer:

npm i @testing-library/react or yarn add @testing-library/react

Import React Testing Library

You can import the library just like any library by running the following command

import {render, screen} from '@testing-library/react'

Here, the render function renders a component just like React, and the screen function is a utility for finding elements the same way the user does.

Rendering the component

You can render the component almost in the same way as Enzyme, the main difference is we use the wrapper in Enzyme and Screen in React Testing Library.

In Enzyme:

describe("Form", () => {
 it("should render a form with two input fields and a submit button", () => {
   const wrapper = shallow(<Form />);
   expect(wrapper.find("form")).toHaveLength(1);
   expect(wrapper.find("label")).toHaveLength(2);
   expect(wrapper.find("input")).toHaveLength(2);
   expect(wrapper.find("button")).toHaveLength(1);
});

In React Testing Library:

describe("Form", () => {
 it("should render a form with two input fields and a submit button", () => {
   render(<Form />);
   expect(screen.getByRole("form")).toBeInTheDocument();
   expect(screen.getAllByRole("textbox")).toHaveLength(2);
   expect(screen.getByRole("button", { name: /submit/i })).toBeInTheDocument();
 });
}

In the React Testing Library, you can access the result of the rendered component without assigning it to a variable by using the "screen" object's functions.

Let’s now look at how you will achieve some tests in Enzyme and see how it is done using React Testing Library.

Focusing an Element

The React Testing Library focuses on testing accessibility through user interactions, including focus. The enzyme also supports checking the focused element and simulating events.

In Enzyme:

const wrapper = mount(<MyComponent />>);
wrapper.find('input').simulate('focus');

In React Testing Library:

const { getByLabelText } = render(<MyComponent />);
const input = getByLabelText('Search');
input.focus();

Selecting Elements

Enzyme gives you different ways to choose which elements to select, such as by using CSS selectors. React Testing Library on the other hand uses more semantic queries, such as getByRole, getByText, and getByLabelText, which simulate how users interact with the application

In Enzyme:

describe('MyComponent', () => {
 it('renders a button', () => {
   const wrapper = mount(<Form />);
   const button = wrapper.find('button');
   expect(button.exists()).toBe(true);
 });
});

In React Testing Library:

describe('MyComponent', () => {
 it('renders a button', () => {
   render(<Form />);
   const button = screen.getByRole('button');
   expect(button).toBeInTheDocument();
});

Updating State and Props:

Enzyme provides methods for updating the state and props of a component, including setState, setProps, and setContext, whereas the React Testing Library does not provide methods for directly updating state or props. Instead, you should simulate user events, such as clicking a button or entering text into an input field, to trigger state and prop changes.

In Enzyme:

describe('MyComponent', () => {
   it('updates state when button is clicked', () => {
     const wrapper = mount(<YourComponent />);
     const button = wrapper.find('button');
     button.simulate('click');
     expect(wrapper.state('count')).toEqual(1);
   });
    it('updates props', () => {
     const wrapper = mount(<YourComponent count={0} />);
     wrapper.setProps({ count: 1 });
     expect(wrapper.prop('count')).toEqual(1);
   });
});

In React Testing Library:

describe('MyComponent', () => {
 it('updates state when button is clicked', () => {
   render();
   const button = screen.getByRole('button');
   fireEvent.click(button);
   const count = screen.getByText('Count: 1');
   expect(count).toBeInTheDocument();
 });

 it('does not directly update props', () => {
   const { rerender } = render();
   rerender();
   const count = screen.getByText('Count: 1');
   expect(count).toBeInTheDocument();
 });
});

Lifecycle Methods

Enzyme provides methods for testing component lifecycle methods, including componentDidMount, componentDidUpdate, and componentWillUnmount

React Testing Library does not provide methods for directly testing lifecycle methods. You should instead test the effects of lifecycle methods by simulating user interactions and watching how the UI changes as a result.

In Enzyme:

describe('MyComponent', () => {
   it('calls componentDidMount', () => {
     const spy = jest.spyOn(MyComponent.prototype, 'componentDidMount');
     const wrapper = mount(<MyComponent />);
     expect(spy).toHaveBeenCalled();
     spy.mockRestore();
   });
});

In React Testing Library:

describe('MyComponent', () => {
   it('updates state on button click', () => {
     const { getByRole } = render(<MyComponent />);
     const button = getByRole('button');
     fireEvent.click(button);
     expect(button.textContent).toBe('Clicked!');
   });
});

Debugging

If you're working on a React project and need a powerful debugging tool, you have a few options to choose from. Enzyme has a debug method that lets you print the component tree to the console. This can help you find problems with your tests. 

React Testing Library also has a debug method, and apart from that, it also provides a screen object that you can use to interact with the rendered components directly in the console. 

In Enzyme: 

describe('MyComponent', () => {
   it('renders correctly', () => {
     const wrapper = mount(<MyComponent />);
     console.log(wrapper.debug());
   });
});

In React Testing Library:

describe('MyComponent', () => {
   it('renders correctly', () => {
     render(<MyComponent />);
     console.log(screen.debug());
   });
});


Advantages of React Testing Library

One of the main reasons why the React Testing Library is gaining popularity in the testing community is the simplicity it provides.

State of JS survey comparison

According to us, the following reasons contribute to its wide acceptance:

  • User-centric approach: React Testing Library provides a user-centric testing approach that encourages developers to test their components in a way that simulates how a user would interact with the app.
  • Semantic queries: The library emphasizes using semantic queries like getByText, getByRole, and getByTestId to select elements in the DOM, making tests more readable and maintainable.
  • Optimized for functional components: React Testing Library is optimized for functional components, which are simpler to test than class components. The library has a clean and straightforward syntax, which makes it easy for developers to write and understand tests.
  • Well documented: React Testing Library has excellent documentation and resources that help developers get up to speed quickly.
  • Focuses on accessibility: React Testing Library encourages users to write more accessible code by avoiding the use of the querySelector API and instead using semantic queries that are more meaningful to users with disabilities.
  • Cross-platform testing: React Testing Library is not just limited to React web applications but also works with React Native. This means developers can use a single testing library for both web and mobile applications, which can save time and effort.

Limitations of React Testing Library

Even though the React Testing Library (RTL) is a strong testing library with lots of advantages, there are still some difficulties that developers might run into. Here are a few challenges that developers may face:

  • Limited control: Unlike other testing libraries, React Testing Library does not offer the same degree of control over the component tree. It is instead designed to test how users interact with an application. This can make it difficult to test certain edge cases or complex interactions.
  • Unable to select the Component state: React Testing Library has the obvious drawback of not being able to test for the state of components. It is generally discouraged to test for the state of the component but sometimes it is necessary to test for the state
  • Accessibility testing: React Testing Library encourages developers to make their applications more accessible but testing for accessibility can still be a challenge. It requires a good understanding of accessibility best practices and may require additional tooling or expertise.
  • Integration testing: React Testing Library is great for testing individual components in isolation, but integration testing can be challenging. The interaction between multiple components or the overall functionality of the application may require additional setup and coordination.
  • Performance testing: React Testing Library is focused on functional testing and user interaction, so it doesn't provide built-in tools for performance testing or load testing. 

During the process of migrating your code from Enzyme to the React Testing Library, you may encounter errors that could halt your progress. To overcome this challenge, you can use Zipy as your error monitoring and debugging tool. Zipy allows you to log real-time error messages, providing you with a better debugging experience and helping you to overcome any obstacles encountered during the migration process.

Ready for Migration?

Congratulations on reaching this far! You’re a fantastic reader!

There is no one "best" tool for all situations. Each tool has its own strengths and weaknesses and can be better suited for certain tasks than others.

But from an up-to-date point of view, Enzyme is outdated, and we should consider migrating to more popular libraries like React Testing Library. In this detailed blog, we have covered all the aspects that you need to know while migrating your test suite from Enzyme to the React Testing Library. 

Now that you know how to migrate to the React Testing Library, you can get started.

Happy Testing!

Call to Action

Feel free to comment or write to us in case you have any further questions at support@zipy.ai. We would be happy to help you. In case you want to explore for your app, you can sign up or book a demo.











Fix bugs faster with Zipy!

Get Started for Free

You might also like

Wanna try Zipy?

Zipy provides you with full customer visibility without multiple back and forths between Customers, Customer Support and your Engineering teams.

The unified digital experience platform to drive growth with Product Analytics, Error Tracking, and Session Replay in one.

SOC 2 Type 2
Zipy is GDPR and SOC2 Type II Compliant
© 2023 Zipy Inc. | All rights reserved
with
by folks just like you
// open links in new tab script