Skip to content
Mig ration F low
frontend testing react

Enzyme React Testing Library

Migrate Enzyme-based tests to React Testing Library, adopting user-centric testing patterns.

Copy for

Using an AI agent? See /agents for MCP, JSON, and llms.txt access.

Enzyme → React Testing Library

Philosophy shift

Enzyme tests implementation details. RTL tests behavior from the user’s perspective.

Rule: If your test would still pass after refactoring component internals without changing behavior, it’s a good RTL test.

Setup

Remove Enzyme:

npm uninstall enzyme enzyme-adapter-react-16 @types/enzyme @types/enzyme-adapter-react-16

Install RTL:

npm install --save-dev @testing-library/react @testing-library/jest-dom @testing-library/user-event

Add to setupTests.ts:

import '@testing-library/jest-dom';

Core transformations

Rendering

// Enzyme
const wrapper = shallow(<MyComponent prop="value" />);
const wrapper = mount(<MyComponent prop="value" />);

// RTL
render(<MyComponent prop="value" />);

Querying elements

Prefer queries in this order: getByRole > getByLabelText > getByText > getByTestId.

// Enzyme
wrapper.find('.btn');
wrapper.find(Button);
wrapper.find('[data-testid="submit"]');

// RTL
screen.getByRole('button', { name: /submit/i });
screen.getByTestId('submit'); // last resort

Asserting text / presence

// Enzyme
expect(wrapper.text()).toContain('Hello');
expect(wrapper.find('.error').exists()).toBe(true);

// RTL
expect(screen.getByText(/hello/i)).toBeInTheDocument();
expect(screen.getByRole('alert')).toBeInTheDocument();

User interactions

// Enzyme
wrapper.find('button').simulate('click');
wrapper.find('input').simulate('change', { target: { value: 'text' } });

// RTL (use userEvent, not fireEvent)
import userEvent from '@testing-library/user-event';

const user = userEvent.setup();
await user.click(screen.getByRole('button'));
await user.type(screen.getByRole('textbox'), 'text');

Async assertions

// Enzyme (manual waits or done callbacks)
await wrapper.update();

// RTL
await screen.findByText(/loaded/i);
await waitFor(() => expect(mock).toHaveBeenCalled());

Testing state / props (anti-pattern in RTL)

// Enzyme (DO NOT port this pattern)
expect(wrapper.state('count')).toBe(1);
expect(wrapper.props().label).toBe('Save');

// RTL — test the rendered output instead
expect(screen.getByText('Count: 1')).toBeInTheDocument();
expect(screen.getByRole('button', { name: /save/i })).toBeInTheDocument();

When NOT to migrate

Pitfalls

Validation checklist

Codemod references

AI Prompt

You are migrating a test file from Enzyme to React Testing Library.

Rules:
1. Replace `shallow` and `mount` with `render` from @testing-library/react.
2. Replace `wrapper.find(...)` with semantic queries in this priority:
   getByRole > getByLabelText > getByText > getByPlaceholderText > getByTestId.
3. Replace `simulate('click')` / `simulate('change', ...)` with userEvent:
   `await user.click(...)`, `await user.type(...)`.
4. Replace `wrapper.update()` and manual waits with `await findBy*` or `await waitFor(...)`.
5. Do NOT port tests that assert on state, props, or instance methods — rewrite them to assert on rendered DOM output.
6. Drop pure shallow-rendering structural tests; replace with behavior-focused assertions.
7. Do not modify the component under test — only the test file.

Migrate the following Enzyme test file:

Query cheatsheet

IntentQuery
Button, link, input by accessible namegetByRole
Form field by its labelgetByLabelText
Non-interactive element by textgetByText
Form field by placeholdergetByPlaceholderText
Element not yet in DOMfindBy* (async)
Element that may not existqueryBy*

References