Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Provides exact Enzyme → React Testing Library migration patterns for React 18 upgrades. Use this skill whenever Enzyme tests need to be rewritten - shallow, mount, wrapper.find(), wrapper.simulate(), wrapper.prop(), wrapper.state(), wrapper.instance(), Enzyme configure/Adapter calls, or any test file that imports from enzyme. This skill covers the full API mapping and the philosophy shift from implementation testing to behavior testing. Always read this skill before rewriting Enzyme tests - do n
.claude/skills/react18-enzyme-to-rtl/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-20 | ✗→✗ | = Same ✗ | — | — |
| case-13 | ✗→✗ | = Same ✗ | — | — |
| case-11 | ✗→✗ | = Same ✗ | — | — |
| case-14 | ✗→✗ | = Same ✗ | — | — |
| case-06 | ✗→✗ | = Same ✗ | — | — |
Enzyme has no React 18 adapter and no React 18 support path. All Enzyme tests must be rewritten using React Testing Library.
Enzyme tests implementation. RTL tests behavior.
jsx// Enzyme: tests that the component has the right internal state expect(wrapper.state('count')).toBe(3); expect(wrapper.instance().handleClick).toBeDefined(); expect(wrapper.find('Button').prop('disabled')).toBe(true); // RTL: tests what the user actually sees and can do expect(screen.getByText('Count: 3')).toBeInTheDocument(); expect(screen.getByRole('button', { name: /submit/i })).toBeDisabled();
This is not a 1:1 translation. Enzyme tests that verify internal state or instance methods don't have RTL equivalents - because RTL intentionally doesn't expose internals. Rewrite the test to assert the visible outcome instead.
For complete before/after code for each Enzyme API, read:
references/enzyme-api-map.md - full mapping: shallow, mount, find, simulate, prop, state, instance, configurereferences/async-patterns.md - waitFor, findBy, act(), Apollo MockedProvider, loading states, error statesjsx// Every Enzyme test rewrites to this shape: import { render, screen, fireEvent, waitFor } from '@testing-library/react'; import userEvent from '@testing-library/user-event'; import MyComponent from './MyComponent'; describe('MyComponent', () => { it('does the thing', async () => { // 1. Render (replaces shallow/mount) render(<MyComponent prop="value" />); // 2. Query (replaces wrapper.find()) const button = screen.getByRole('button', { name: /submit/i }); // 3. Interact (replaces simulate()) await userEvent.setup().click(button); // 4. Assert on visible output (replaces wrapper.state() / wrapper.prop()) expect(screen.getByText('Submitted!')).toBeInTheDocument(); }); });
getByRole - matches accessible roles (button, textbox, heading, checkbox, etc.)getByLabelText - form fields linked to labelsgetByPlaceholderText - input placeholdersgetByText - visible text contentgetByDisplayValue - current value of input/select/textareagetByAltText - image alt textgetByTitle - title attributegetByTestId - data-testid attribute (last resort)Prefer getByRole over getByTestId. It tests accessibility too.
jsx// Enzyme with context: const wrapper = mount( <ApolloProvider client={client}> <ThemeProvider theme={theme}> <MyComponent /> </ThemeProvider> </ApolloProvider> ); // RTL equivalent (use your project's customRender or wrap inline): import { render } from '@testing-library/react'; render( <MockedProvider mocks={mocks} addTypename={false}> <ThemeProvider theme={theme}> <MyComponent /> </ThemeProvider> </MockedProvider> ); // Or use the project's customRender helper if it wraps providers
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-20 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-13 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-11 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-14 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-06 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-09 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-22 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-18 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-17 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-08 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-15 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-03 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-04 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-07 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-10 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-16 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-12 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-21 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-01 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-02 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-05 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
case-19 | fail→fail | — | — | — | — | — | — | — | — | — | — | — | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of -100 percentage points is the difference between those two pass rates over the 22 comparable cases. 1 case got worse with the skill loaded, and it is included in that figure.
The per-case answers from this run were removed by the retention sweep, so the case table below shows the verdicts without the text either arm produced. The counts above were recorded at the time and are unaffected. Answers are now kept for 180 days.
Other measured skills in the registry, with their headline benchmark lift.