npx skills add ...
npx skills add affaan-m/ecc --skill react-testing
React component testing with React Testing Library, Vitest/Jest, MSW for network mocking, accessibility assertions with axe, and the decision boundary between component tests and Playwright/Cypress end-to-end runs. Use when writing or fixing tests for React components, hooks, or pages.
npx skills add affaan-m/ecc --skill react-testing
Comprehensive React testing patterns for behavior-focused component tests, custom hook tests, accessibility assertions, and network-level mocking.
Test what the user sees and does, not implementation details.
A test should:
userEventA test should NOT:
| Runner | When | Note |
|---|---|---|
| Vitest | Vite, Remix, modern setups | Faster, native ESM, Jest-compatible API |
| Jest | Next.js, CRA, established repos | Default for many React projects |
| Playwright Component Testing | Real browser engine needed | Use when JSDOM lacks the required feature |
| Cypress Component Testing | Real browser, Cypress already in use | Alternative to Playwright CT |
Pick one. Do not run RTL + Vitest AND Playwright CT in the same repo unless you have a clear lane separation.
React Testing Library exposes queries in three tiers — use top-down:
getByRole, getByLabelText, getByPlaceholderText, getByText, getByDisplayValuegetByAltText, getByTitlegetByTestIdVariants:
getBy* — throws if no matchqueryBy* — returns null (use for "assert absence")findBy* — async, returns a Promise (use for elements that appear after async work)userEventawait userEvent callsuserEvent.setup() once per test, reuse the returned useruserEvent simulates a real browser sequence; fireEvent dispatches a single synthetic event — prefer userEventNever setTimeout + assertion — flaky. Use the matchers above.
Mock Service Worker mocks at the network layer. The component, hooks, and fetch library all behave exactly as in production.
Configure onUnhandledRequest: "error" so any unmocked request fails the test loudly — silent passes are worse than red.
Wrap providers once in a test-utils.tsx:
Then import { renderWithProviders, screen } from "test-utils" in every test file.
actwrapperRun axe in component tests for every interactive component. Catches:
Cross-link: skills/accessibility/SKILL.md for the broader a11y testing playbook.
Snapshots of rendered output:
Acceptable snapshot uses:
formatInvoice(invoice) -> stable string)For visual regression on components, use Playwright/Cypress screenshots or Percy/Chromatic — actual visual diffs, not DOM strings.
JSDOM (used by Vitest/Jest) cannot:
For any of those, use Playwright Component Testing (component test in real browser) or full E2E. See e2e-testing skill.
Decision boundary:
| Layer | Target |
|---|---|
| Pure utilities | >=90% |
| Custom hooks | >=85% |
| Presentational components | >=80% — behavior, not lines |
| Container components | >=70% — golden paths + error states |
| Pages | E2E covered separately; smoke test minimum |
Configure via vitest.config.ts / jest.config.js:
container.querySelector("...") — bypasses accessibility queries, lets tests pass when real users would failjest.mock("react", ...) — never mock React. Refactor the component insteadact() warnings — they signal real bugs (state update after unmount, missing async wrapping)it.skip() removed — your test does not actually assert what you thinkFor new components:
react-reviewer (reviews test quality during code review), tdd-guide (enforces TDD process)/react-test, /react-review