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 Mar 04, 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 developers, we frequently encounter situations where we struggle to determine which library is the most appropriate for our needs and to understand the differences between various options.

When it comes to component-level testing in React JS, developers have several testing libraries and tools, including Enzyme, Jest, and the Testing Library. Component testing is an essential part of modern web development, especially for complex web applications built on top of 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?

We know that, under the hood,  React is a component-based UI library,  with two types of components: functional and class components. Functional components are like functions in JavaScript, and class components are like classes in JavaScript. One needs to have a basic understanding of JavaScript concepts before learning React.

Most probably, you will be using the functional component, and the core React  developers also encourage you to write functional components. Each component will be part of the layout logic of your application, and together they make your application complete.

If your component is just some simple UI, you don't need that many functionality checks and can conduct various tests like component testing, unit testing, etc. However, for more complex components that contain states or rely on certain behaviors, it's important to perform thorough unit testing in React to ensure that they're working as expected. 

Component testing is the type of testing where you focus on individual units of code, in this case, components. It can be a class or functional component, and the main intention of doing component testing is to ensure that each component is working as expected.

Most web applications have some type of input form to collect information from the user, and it is important to validate that the implementation is working as expected, or else you may lose a potential customer.

Component testing helps you ensure that each and every function that you have implemented is working correctly and reliably, which leads to a better 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

With Enzyme you can also manipulate, traverse, and, in some ways, simulate runtime given the output. Enzyme's API is meant to be intuitive and flexible by mimicking jQuery's API for DOM manipulation and traversal.

Enzyme has had only one release in the last three years, and it does not appear that they intend to continue development. As developers, we know that the field is always changing, and Enzyme doesn't seem to be able to offer the best testing features for modern web development, and it doesn't look like that will change any time soon.

Although an official Enzyme adapter for React 17 was never released, the creator of Enzyme published a temporary adapter that has become the de facto default adapter with 16 million downloads. However, this has only delayed the inevitable death of Enzyme and given developers a false sense of safety.

By the end of 2021, Wojciech Maj, the project's creator, had officially announced that he had stopped working on it and that no further updates could be expected.

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 checks if the onSubmit function is called with the form data when the submit button is clicked. It creates a mock function mockOnSubmit and passes it as a prop to the Form component.

It then gets the input fields and submit button using the screen object from @testing-library/react, simulates changes to the input fields and a click event on the submit button using fireEvent from the same library, and asserts that mockOnSubmit was called with the expected data.

If you have gone through the code, you can see that, unlike Enzyme, React Testing Library does not provide direct access to the component state or props. Instead, it encourages you to test your component from the user's perspective, by interacting with the rendered output and making assertions about what is displayed.

Here are the differences found in our test suite:

  • Instead of using Enzyme's shallow or mount methods, you would typically use the render method from @testing-library/react.
  • Instead of using Enzyme's find method to search for elements, you would typically use a DOM query method from @testing-library/dom, such as getByLabelText, getByRole, or getByText.
  • Instead of directly accessing the component's state or props, you would typically interact with the component by simulating user events using the fireEvent utility from @testing-library/react.
  • You would generally not use Enzyme's debug method to log the component's output, but would instead use the prettyDOM method from @testing-library/dom to get a string representation of the component's rendered output.

Keep in mind that this is just a general overview, and depending on your needs as you work on code migration, you might want to add some additional test cases.

As it is visible in this example, Enzyme’s shallow renderer does not render sub-components, whereas React Testing Library’s render method renders the component and all sub-components just like Enzyme’s mount method.

In the React Testing Library, you don't always need to assign the render result to a variable; that is, it doesn’t always need a wrapper. You can simply access the rendered output by calling functions on the screen object.

The other good thing to know is that React Testing Library automatically cleans up the environment after each test, so you don't need to call cleanup in an afterEach or beforeEach function.

One of the things people quickly learn to love about React Testing Library is how it encourages you to write more accessible applications (because if they're not accessible, then it's harder to test).

However, starting from scratch is recommended, even by the creator of Enzyme. Because React Testing Library is not a drop-in replacement for Enzyme, but rather an alternative to 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
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.

Want to solve customer bugs even before they're reported?

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