Rewrite Enzyme tests for React 18 into React Testing Library behavior tests. Use when a test imports enzyme, uses shallow, mount, wrapper.find(), wrapper.simulate(), wrapper.prop(), wrapper.state(), wrapper.instance(), Enzyme configure/Adapter, or needs React 18-compatible RTL migration patterns.
Use the project's custom render helper if it wraps MockedProvider, ThemeProvider, routing, store, or i18n context. Read references/async-patterns.md for waitFor, findBy, act(), Apollo MockedProvider, loading states, and error states.
Criteria
Replace every Enzyme import and adapter setup; no test file still imports enzyme.
Rewrite internal state, instance, and prop assertions as visible behavior or observable side effects.
Prefer accessible RTL queries over selectors and data-testid.
Use userEvent for user interactions and await async interactions.
Preserve provider context through project customRender or inline wrappers.
Cover loading, success, and error states where the Enzyme test previously relied on implementation timing.
Gotchas
No 1:1 translation: replacing wrapper.find() with container.querySelector() preserves brittle implementation testing.
Do not assert child props directly: mock the child only when the child is an external boundary; otherwise assert what the user sees.
Use findBy or waitFor for async UI: immediate getBy* assertions can race React 18 updates.
Delete Enzyme configure/Adapter calls: keeping them can hide migration incompleteness.
Progressive disclosure and bundled resources
references/enzyme-api-map.md: complete Enzyme API mapping for shallow, mount, find, simulate, prop, state, instance, configure, and Adapter.
references/async-patterns.md: async RTL patterns for waitFor, findBy, act(), Apollo MockedProvider, loading states, and error states.
Migration shorthand
Treat shallow/mount as the signal to rewrite render strategy. Use getByDisplayValue for current input/select/textarea values.
No migrated file imports enzyme or configures an Enzyme Adapter.
shallow, mount, wrapper.find(), wrapper.simulate(), wrapper.prop(), wrapper.state(), and wrapper.instance() are removed or explicitly reported as remaining blockers.
Query priority favors getByRole and avoids getByTestId unless justified.
Async behavior uses findBy, waitFor, or awaited userEvent as appropriate.
Provider context is preserved with customRender or equivalent wrappers.
Targeted tests were run or a concrete blocker is reported.
1---2name: react18-enzyme-to-rtl-33description: Rewrite Enzyme tests for React 18 into React Testing Library behavior tests. Use when a test imports enzyme, uses shallow, mount, wrapper.find(), wrapper.simulate(), wrapper.prop(), wrapper.state(), wrapper.instance(), Enzyme configure/Adapter, or needs React 18-compatible RTL migration patterns.4---56<!-- Generated from harness/github-copilot/plugins/react18-upgrade/skills/react18-enzyme-to-rtl/SKILL.md by harness/claude-code/scripts/convert_from_copilot.py. Edit the source, not this file. -->78# React 18 Enzyme to RTL migration910Migrate unsupported Enzyme tests to React Testing Library by replacing implementation assertions with user-visible behavior, accessible queries, provider-aware renders, and async-safe interactions.1112## When to invoke1314- "Rewrite this Enzyme test for React 18."15- "Migrate shallow and mount tests to React Testing Library."16- "Replace wrapper.find() and simulate() in this test."17- "This file imports enzyme and blocks our React 18 upgrade."18- "Convert wrapper.state(), wrapper.prop(), or wrapper.instance() assertions."1920## Prerequisites and context2122- Enzyme has no React 18 adapter and no supported React 18 migration path.23- Use React Testing Library imports such as `render`, `screen`, `fireEvent`, and `waitFor` from `@testing-harness/github-copilot/react`.24- Prefer `userEvent` from `@testing-harness/github-copilot/user-event` for real user interactions.25- Use project-specific `customRender` helpers when they already wrap providers.2627## Philosophy shift2829Enzyme tests component internals; RTL tests observable behavior. Do not translate APIs 1:1.3031| Enzyme habit | Why it fails in RTL | Replacement mindset |32| --- | --- | --- |33| `wrapper.state('count')` | RTL does not expose component state. | Assert visible count, enabled state, submitted text, or emitted output. |34| `wrapper.instance().handleClick` | Function components have no instance and internals are not user behavior. | Click the control and assert the result. |35| `wrapper.find('Button').prop('disabled')` | Props are implementation details. | Query by role and assert `toBeDisabled()`. |36| `shallow(<Component />)` | Shallow rendering hides integrated behavior. | Render the component with required providers and assert user-visible output. |3738```jsx39// Enzyme: tests internals40expect(wrapper.state('count')).toBe(3);41expect(wrapper.instance().handleClick).toBeDefined();42expect(wrapper.find('Button').prop('disabled')).toBe(true);4344// RTL: tests behavior45expect(screen.getByText('Count: 3')).toBeInTheDocument();46expect(screen.getByRole('button', { name: /submit/i })).toBeDisabled();47```4849## Core rewrite template5051```jsx52import { render, screen, fireEvent, waitFor } from '@testing-harness/github-copilot/react';53import userEvent from '@testing-harness/github-copilot/user-event';54import MyComponent from './MyComponent';5556describe('MyComponent', () => {57 it('does the thing', async () => {58 render(<MyComponent prop="value" />);5960 const button = screen.getByRole('button', { name: /submit/i });61 await userEvent.setup().click(button);6263 expect(screen.getByText('Submitted!')).toBeInTheDocument();64 });65});66```6768## API migration map6970Read `references/enzyme-api-map.md` for full before/after examples covering `shallow`, `mount`, `find`, `simulate`, `prop`, `state`, `instance`, `configure`, and `Adapter` setup.7172| Enzyme API | RTL direction |73| --- | --- |74| `shallow(<Component />)` | `render(<Component />)` with dependencies mocked only at module boundaries. |75| `mount(<Component />)` | `render(<Component />)` wrapped in required providers. |76| `wrapper.find(selector)` | `screen.getByRole`, `getByLabelText`, `getByText`, or another user-facing query. |77| `wrapper.simulate('click')` | `await userEvent.setup().click(element)`; use `fireEvent` for low-level events only. |78| `wrapper.prop('x')` | Assert visible output or child behavior caused by the prop. |79| `wrapper.state('x')` | Assert the DOM, callback, network mock, or side effect that reflects the state. |80| `wrapper.instance()` | Remove direct instance testing; exercise the public UI. |81| `Enzyme.configure({ adapter })` | Delete Enzyme setup and use RTL/Jest setup such as `@testing-harness/github-copilot/jest-dom`. |8283## RTL query priority8485Use queries in this order; `getByTestId` is the last resort.86871. `getByRole` for accessible roles such as button, textbox, heading, checkbox.882. `getByLabelText` for form fields linked to labels.893. `getByPlaceholderText` for input placeholders.904. `getByText` for visible text content.915. `getByDisplayValue` for current `input`, `select`, or `textarea` value.926. `getByAltText` for image alt text.937. `getByTitle` for title attributes.948. `getByTestId` for `data-testid` only when accessible queries cannot express the behavior.9596## Providers and async behavior9798```jsx99// Enzyme with context100const wrapper = mount(101 <ApolloProvider client={client}>102 <ThemeProvider theme={theme}>103 <MyComponent />104 </ThemeProvider>105 </ApolloProvider>106);107108// RTL equivalent109render(110 <MockedProvider mocks={mocks} addTypename={false}>111 <ThemeProvider theme={theme}>112 <MyComponent />113 </ThemeProvider>114 </MockedProvider>115);116```117118Use the project's custom render helper if it wraps `MockedProvider`, `ThemeProvider`, routing, store, or i18n context. Read `references/async-patterns.md` for `waitFor`, `findBy`, `act()`, Apollo `MockedProvider`, loading states, and error states.119120## Criteria121122- [ ] Replace every Enzyme import and adapter setup; no test file still imports `enzyme`.123- [ ] Rewrite internal state, instance, and prop assertions as visible behavior or observable side effects.124- [ ] Prefer accessible RTL queries over selectors and `data-testid`.125- [ ] Use `userEvent` for user interactions and await async interactions.126- [ ] Preserve provider context through project `customRender` or inline wrappers.127- [ ] Cover loading, success, and error states where the Enzyme test previously relied on implementation timing.128129## Gotchas130131- **No 1:1 translation**: replacing `wrapper.find()` with `container.querySelector()` preserves brittle implementation testing.132- **Do not assert child props directly**: mock the child only when the child is an external boundary; otherwise assert what the user sees.133- **Use `findBy` or `waitFor` for async UI**: immediate `getBy*` assertions can race React 18 updates.134- **Delete Enzyme configure/Adapter calls**: keeping them can hide migration incompleteness.135136## Progressive disclosure and bundled resources137138- `references/enzyme-api-map.md`: complete Enzyme API mapping for `shallow`, `mount`, `find`, `simulate`, `prop`, `state`, `instance`, `configure`, and `Adapter`.139- `references/async-patterns.md`: async RTL patterns for `waitFor`, `findBy`, `act()`, Apollo `MockedProvider`, loading states, and error states.140141## Migration shorthand142143Treat `shallow/mount` as the signal to rewrite render strategy. Use `getByDisplayValue` for current `input/select/textarea` values.144145## Output template146147```markdown148## Enzyme to RTL migration result149150**Status:** complete | partial | blocked151**Files changed:** <count>152**React 18 readiness:** pass | fail153154| File | Enzyme APIs removed | RTL patterns used | Remaining blockers |155| --- | --- | --- | --- |156| `<test file>` | `shallow`, `wrapper.find()` | `render`, `screen.getByRole`, `userEvent` | <none or blocker> |157158### Validation159- Enzyme imports removed: pass | fail160- Targeted test command: pass | fail | not run (<reason>)161```162163## Quality gate164165- [ ] No migrated file imports `enzyme` or configures an Enzyme `Adapter`.166- [ ] `shallow`, `mount`, `wrapper.find()`, `wrapper.simulate()`, `wrapper.prop()`, `wrapper.state()`, and `wrapper.instance()` are removed or explicitly reported as remaining blockers.167- [ ] Assertions target user-visible DOM, accessibility state, callbacks, or externally observable effects.168- [ ] Query priority favors `getByRole` and avoids `getByTestId` unless justified.169- [ ] Async behavior uses `findBy`, `waitFor`, or awaited `userEvent` as appropriate.170- [ ] Provider context is preserved with `customRender` or equivalent wrappers.171- [ ] Targeted tests were run or a concrete blocker is reported.
Run npx skillmds@latest add paulasilvatech/react18-enzyme-to-rtl-3 in your terminal (requires Node.js), paste this page's agent-chat prompt into Claude, Cursor, or any MCP-connected agent, or download the SKILL.md file and copy it into your agent's skills directory.
Rewrite Enzyme tests for React 18 into React Testing Library behavior tests. Use when a test imports enzyme, uses shallow, mount, wrapper.find(), wrapper.simulate(), wrapper.prop(), wrapper.state(), wrapper.instance(), Enzyme configure/Adapter, or needs React 18-compatible RTL migration patterns. It is listed under Web & Frontend on SkillMD.
This skill has not completed SkillMD's automated safety review yet. SkillMD never runs a skill's scripts for you; review the SKILL.md before installing.
This skill is tagged as working with Claude Code, Claude.ai, OpenAI Codex. SKILL.md is an open format, so most agents that read a skills directory can load it too.
Yes. Installing skills from SkillMD is free, and the skill stays under its author's original license.
paulasilvatech (@paulasilvatech) published this skill. Their other Agent Skills are listed on their SkillMD profile.