Running Enzyme Tests in React 18: Our Success Story.

Like many other companies with mature software, we found ourselves at a crossroads with our React application. The app, initially developed in early 2019, was built with React 16 and used Enzyme for unit testing. Over the past five years, the app grew, evolved, gained new features, and went though minor and major refactorings. Obviously, as responsible engineers we always maintained unit test coverage around 70-80%. However, our React version had remained unchanged.

By the end of 2024, it became clear that we couldn't delay the upgrade to React 18 any longer. But we faced a challenge: despite gradually adopting React Testing Library starting in 2021, we still had around 500 tests written with Enzyme. Unfortunately, enzyme-adapter-react-16 had been abandoned a while ago, and changes in React 18 engine made it impossible to use Enzyme at all.

We began creating tickets to refactor those Enzyme-based tests to RTL, but quickly realized that given our limited number of engineers, plus our ongoing business and maintenance obligations, rewriting all the tests in a reasonable time frame simply wasn't feasible.

We decided to explore a different solution: running two versions of React in our application. React 18 would be used for development and RTL unit tests, while React 16 would be retained exclusively for the Enzyme-based tests. This approach allowed us to upgrade without immediately refactoring all of our tests, buying us the time we needed.

Step #1: Two React versions

The first step was to configure our app to install both React versions. Fortunately, modern npm supports this kind of setup through aliasing.

We added the following aliases to our package.json:

"react-16": "npm:react@16.13.0",
"react-dom-16": "npm:react-dom@16.13.0"

Step #2: Identifying Enzyme Tests

Next, we needed to identify all the files using Enzyme. We did this by searching for files containing import { mount, shallow } from 'enzyme'; and then renaming those files to use the .enzyme.spec.js extension. This made it easier to differentiate them from the RTL-based tests.

Step #3: Configuring Jest to Run Two Projects

To handle both sets of tests, we configured Jest to run two separate projects—one for RTL and one for Enzyme. Below is our jest.config.js setup:

module.exports = {
  ...commonConfig,
  projects: [
    {
      displayName: 'R16 ENZYME - CONVERT ME ASAP!',
      testMatch: ['**/*.enzyme.spec.js'], // Only match enzyme-specific tests
      moduleNameMapper: {
        '^react(/.*)?$': 'react-16$1', // Map to old React 16 for Enzyme tests
        '^react-dom(/.*)?$': 'react-dom-16$1', // Map to old React-DOM 16 for Enzyme tests
      },
      setupFiles: ['<rootDir>/enzyme.config.js'],
      setupFilesAfterEnv: ['<rootDir>/jest-setup-enzyme.js'],
      snapshotSerializers: ['enzyme-to-json/serializer'],
      ...otherConfigs,
    },
    {
      displayName: 'R18 RTL',
      testMatch: ['**/*.spec.js', '**/*.spec.jsx'], // Match other test files
      testPathIgnorePatterns: [
        '\\.enzyme\\.spec\.js$', // Ignore Enzyme tests in this configuration
      ],
      setupFilesAfterEnv: ['<rootDir>/jest-setup.js'], // RTL-specific setup
      ...otherConfigs,
    },
  ],
};

In the React 16 project, we mapped react and react-dom to the aliased versions (react-16 and react-dom-16) in node_modules, ensuring that Jest would run with the Enzyme configuration for the .enzyme.spec.js files. Meanwhile, in the React 18 project, we used the jest-setup.js configuration and excluded the Enzyme test files.

Step #4: Enzyme Configuration

For Enzyme, we used the following enzyme.config.js file to configure the Enzyme adapter for React 16:

import 'regenerator-runtime/runtime';
import { configure } from 'enzyme';
import Adapter from 'enzyme-adapter-react-16';

configure({ adapter: new Adapter() });

Conclusion

This isn't a perfect solution, but it allowed us to postpone the full refactor of 500 unit tests while enabling the upgrade to React 18. By maintaining dual React versions, we unblocked our path forward, striking a balance between immediate progress and technical debt management.

Our journey highlights that sometimes, pragmatic solutions serve as effective stepping stones. We plan to eventually migrate all tests to RTL, but in the meantime, this approach kept us moving without compromising our ability to deliver quality software.

0
Subscribe to my newsletter

Read articles from Andrew Arhangelski directly inside your inbox. Subscribe to the newsletter, and don't miss out.

Written by

Andrew Arhangelski
Andrew Arhangelski