
React Testing
Write behavior-focused React tests with Testing Library, Vitest or Jest, MSW, and axe—with clear RTL vs E2E boundaries.
Install
npx skills add https://github.com/affaan-m/ecc --skill react-testingWhat is this skill?
- Core rule: test what users see and do—roles, labels, userEvent—not state, child props, or render counts
- Runner guidance table for Vitest vs Jest and when to escalate to Playwright or Cypress E2E
- MSW patterns for network-level HTTP mocking in component tests
- Accessibility checks with axe integrated into RTL flows
- Migration notes from Enzyme and legacy class-component test styles
Adoption & trust: 1 installs on skills.sh; 211k GitHub stars; trending (+100% hot-view momentum).
Recommended Skills
Agent Browservercel-labs/open-agents
Tddmattpocock/skills
Use My Browserxixu-me/skills
Test Driven Developmentobra/superpowers
Verification Before Completionobra/superpowers
Webapp Testinganthropics/skills
Journey fit
Primary fit
Canonical shelf is Ship/testing because the skill’s purpose is quality gates before release, even though you invoke it while coding components. It targets test design, mocking, and accessibility assertions—the core testing subphase—not raw feature implementation.
SKILL.md
READMESKILL.md - React Testing
# React Testing Comprehensive React testing patterns for behavior-focused component tests, custom hook tests, accessibility assertions, and network-level mocking. ## When to Activate - Writing tests for React components, custom hooks, or pages - Adding test coverage to legacy untested components - Migrating from Enzyme or class-component-era patterns to React Testing Library - Setting up Vitest or Jest for a new React project - Mocking HTTP requests in tests - Asserting accessibility violations - Deciding which tests belong in RTL vs Playwright Component Testing vs full E2E ## Core Principle Test what the user sees and does, not implementation details. A test should: - Render the component with the same providers it has in production - Interact with it via accessible queries (role, label) and `userEvent` - Assert visible output and observable side effects (callback fired, request sent) A test should NOT: - Inspect component state, props passed to children, or which hooks were called - Mock React itself or framework hooks - Assert on the number of renders or DOM structure beyond what affects users ## Library Choice | 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. ## Query Priority React Testing Library exposes queries in three tiers — use top-down: 1. **Accessible to everyone**: `getByRole`, `getByLabelText`, `getByPlaceholderText`, `getByText`, `getByDisplayValue` 2. **Semantic**: `getByAltText`, `getByTitle` 3. **Test IDs (escape hatch)**: `getByTestId` ```tsx // Best screen.getByRole("button", { name: /save/i }); // OK for inputs screen.getByLabelText("Email"); // Last resort screen.getByTestId("save-btn"); ``` Variants: - `getBy*` — throws if no match - `queryBy*` — returns `null` (use for "assert absence") - `findBy*` — async, returns a Promise (use for elements that appear after async work) ## User Interaction with `userEvent` ```tsx import userEvent from "@testing-library/user-event"; test("submits the form", async () => { const user = userEvent.setup(); const onSubmit = vi.fn(); render(<UserForm onSubmit={onSubmit} />); await user.type(screen.getByLabelText("Email"), "user@example.com"); await user.click(screen.getByRole("button", { name: /save/i })); expect(onSubmit).toHaveBeenCalledWith({ email: "user@example.com" }); }); ``` - Always `await` userEvent calls - Call `userEvent.setup()` once per test, reuse the returned `user` - `userEvent` simulates a real browser sequence; `fireEvent` dispatches a single synthetic event — prefer `userEvent` ## Async Patterns ```tsx // Element that appears after async work expect(await screen.findByText("Loaded")).toBeInTheDocument(); // Side effect assertion await waitFor(() => expect(saveSpy).toHaveBeenCalled()); // Element that should disappear await waitForElementToBeRemoved(() => screen.queryByText("Loading")); ``` Never `setTimeout` + assertion — flaky. Use the matchers above. ## Network Mocking with MSW Mock Service Worker mocks at the network layer. The component, hooks, and fetch library all behave exactly as in production. ### Setup ```ts // test/setup.ts import { setupServer } from "msw/node"; import { http, HttpResponse }