Words by Vernacchia

Goodbye Enzyme


I’ve showed my age in the previous post, so I won’t do it again. But, for the purposes of this post, I will say that I’ve been a professional in the industry prior to Enzyme, during its height, and after it (i.e. now).

This leads me to say, using my expert opinion, “Goodbye Enzyme, and good riddance. You will not be missed.”

That may seem harsh, but I stand behind it. I’ve recently removed Enzyme from multiple, highly used codebases at my work and have see the horrors caused by Enzyme. I’ll cover some of these in this article, comparing it to React Testing Library.

Enzyme? React Testing Library (RTL)? What are they?

They are both testing frameworks aiming to make it easier to test React components. They both have their own philosophies and ways of doing things.

Enzyme

Enzyme is a JavaScript Testing utility for React that makes it easier to test your React Components’ output. You can also manipulate, traverse, and in some ways simulate runtime given the output.

RTL

You want to write maintainable tests that give you high confidence that your components are working for your users. As a part of this goal, you want your tests to avoid including implementation details so refactors of your components (changes to implementation but not functionality) don’t break your tests and slow you and your team down.

These differing philosophies are the main reason why I prefer RTL over Enzyme. Enzyme is more focused on the “output” of a component. Additionally, they talk about traversing, manipulating, and simulating the output, which does not align with how actual users would be interacting with it.

On the other hand, RTL is more focused on the “functionality” of a component. It is more concerned with how a user would interact with the component, and how the component would respond to that interaction. ❤️

Problems with Enzyme

Testing async scenarios is difficult

One of the biggest issues I’ve had with Enzyme is testing async scenarios. Unless you’re purely working on a presentational component, most components will have some form of async natur. Something like data loading, form input and validation, etc.

You’ll need to perform an action, wait for the async action to complete, then move on with your test.

This is a nightmare to do in Enzyme. Code using setTimeout, setInterval, setImmediate, await Promise.resolve() or similar is likely trying to account for these async actions. It’s HACKY… and I never liked doing it, even though there was no other way.

RTL handles this much better in two ways, maybe more.

First, RTL provides a waitFor utility. It allows you to wait for a condition to be true before moving on with the test. This can be used in a sync or async manner. Using it in an async manner will ensure that the async action has completed before moving on.

Second, the @testing-library/user-event package provides utility methods to help deal with user interactions. When a user interaction triggers an async action via a command like click, type, clear, etc., the command can be awaited to ensure the async action has completed.

This functionality is only in the newer versions of user-event, but it’s already proved amazingly helpful.

Simulating interactions

When testing user interactions, Enzyme simulates events in a programmatic way by dispatching DOM events.

IMO, this doesn’t mimic real user interaction as there may be multiple events that can fire in a single interaction.

For example, how many events fire when clicking a button? Well, there’s quite a few:

  1. mouseover
  2. mousedown
  3. mouseup
  4. click (interesting that this is after mouseup… hmmm haha)
  5. mouseout

If you’re only simulating a “click” you’re missing out on a lot of the events that would happen during an interaction. This leads to less confidence in your tests (IMO).

Or, if we take an example directly from their website:

For example, when a user types into a text box, the element has to be focused, and then keyboard and input events are fired and the selection and value on the element are manipulated as they type.

RTL, on the other hand, uses the @testing-library/user-event package to simulate user interactions (you can still use the fireEvent method though if you really want to). This takes into account the multitude events that could happen when a user interacts with a component.

Having access to props

This is a killer… and I hate it. I’ve seen it abused so many times.

You can see a basic form below.

const Component = ({ onSubmit }) => {
    return (
        <form onSubmit={onSubmit}>
            <input type="text" name="input1" />
            <input type="text" name="input2" />
            <button type="submit" onSubmit={onSubmit}>
                Submit
            </button>
        </form>
    );
};

It’s likely there’s some form of validation on the inputs and once the form is submitted some action has to take place.

In Enzyme, you can access the props of a component and call the onSubmit function directly, allowing something like:

it("should submit form", () => {
    const someAsyncAction = jest.fn();
    const onSubmit = () => {
        someAsyncAction();
    };
    const wrapper = shallow(<Component onSubmit={onSubmit} />);

    wrapper.find("form").props("onSubmit")();

    expect(someAsyncAction).toHaveBeenCalled();
});

This is hard to look at. There’s no user interaction and no validity checks. We’re just calling a function directly (yes, I have seen this in real life).

While this is a simple example, you can see how it would open the door for lazy testing (and it does).

/endrant

While Enzyme was an absolute killer when it first came out, enabling the testing of UI components like we’d not seen before, it’s not fit for purpose anymore.

It actively encourages bad practices, can’t be used past v16 of React (with an official adapter), isn’t maintained, and has a philosophy that doesn’t align with how users interact with components.

Please, please consider using RTL for your testing needs. It’s a much better framework that encourages better testing.

Goodbye Enzyme, and good riddance. You will not be missed.

Until next time...