Org Skills

react-testing

Design and write a React test strategy and the tests themselves — unit tests with Vitest/Jest and React Testing Library, API mocking with MSW, hook testing, component visual/interaction tests in Storybook, and end-to-end tests with Playwright.

Download .zip Raw Source
When agents use itUse this whenever the user asks how to test a React component, hook, form, or page; wants to set up a testing pipeline; asks about coverage, flaky tests, mocking fetch/axios, testing async state, or "what should I test". Also use it when generating tests for React code the user has just written, even if they don't say the word "test".

Install

Copilot (VS Code, Visual Studio, Copilot CLI and github.com) reads skills from the repository — commit them so the whole team gets them.

npx skills add AGCO-Global/org-skills --skill react-testing -a github-copilot
# or with the org installer (adds .github/skills/react-testing):
npx -y github:AGCO-Global/org-skills add skill react-testing

Uses the open skills CLI. Works with Claude Code, Codex, Cursor, Copilot, Gemini CLI, OpenCode, Windsurf and 60+ others — it asks which agent to install into.

npx skills add AGCO-Global/org-skills --skill react-testing
# user-level instead of project-level:
npx skills add AGCO-Global/org-skills --skill react-testing -g

Installs the frontend-react-skills plugin, which bundles all Frontend / React skills and keeps them updated.

/plugin marketplace add AGCO-Global/org-skills
/plugin install frontend-react-skills@org-skills
# or just this skill, in this repository:
npx skills add AGCO-Global/org-skills --skill react-testing -a claude-code

Use Download .zip above, then upload it under Customize → Skills → Upload skill. Team and Enterprise admins can sync this repo as a plugin marketplace instead.

Installs into .agents/skills/, which Codex reads.

npx skills add AGCO-Global/org-skills --skill react-testing -a codex

Installs into Cursor's skills folder.

npx skills add AGCO-Global/org-skills --skill react-testing -a cursor

Installs into Gemini CLI's skills folder.

npx skills add AGCO-Global/org-skills --skill react-testing -a gemini-cli

Any tool with rules, instructions or custom prompts: use Copy SKILL.md above and paste it in. It is plain Markdown.

Commands use your normal git sign-in to GitHub, so they work while the repository is private. Node.js 20+ required.

Skill contents

React Testing

Tests exist to let the team refactor without fear. Optimise for confidence per minute of maintenance, not for coverage percentage.

1. The testing pyramid for React (what to write, how much)

Layer Tool Share Tests what
Static TypeScript strict, ESLint, typescript-eslint Free Type errors, unused code, hook rules
Unit (pure) Vitest (Vite) / Jest (CRA, Next.js legacy) ~20% Domain logic in model/, utils, reducers, selectors — no React
Component / integration Vitest + React Testing Library + MSW ~60% A feature slice rendered with real children, real hooks, mocked network
Visual / interaction Storybook + @storybook/test + Chromatic (optional) ~10% Design-system primitives, states, a11y
End-to-end Playwright ~10% 5–15 critical user journeys against a real build

Push the user toward integration tests at the feature boundary. Testing a single <Button> with 40 cases is low value; testing "user fills the checkout form, submits, sees confirmation" with the real form, real validation, and mocked API is high value.

2. Setup that prevents most flakiness

// vitest.config.ts
export default defineConfig({
  test: {
    environment: 'jsdom',            // or 'happy-dom' for speed
    globals: true,
    setupFiles: ['./src/test/setup.ts'],
    css: false,
    coverage: { provider: 'v8', thresholds: { statements: 70 } },
  },
});
// src/test/setup.ts
import '@testing-library/jest-dom/vitest';
import { server } from './msw/server';
beforeAll(() => server.listen({ onUnhandledRequest: 'error' }));
afterEach(() => { server.resetHandlers(); cleanup(); });
afterAll(() => server.close());

onUnhandledRequest: 'error' is the single most valuable line: any real network call fails loudly instead of hanging.

Provide a renderWithProviders helper in src/test/ that wraps in the real QueryClientProvider (with retry: false), router, theme, and i18n. Every component test uses it; nobody hand-wires providers.

3. Component tests with Testing Library — the rules

  • Query by role and accessible name first. getByRole('button', { name: /save/i }). It tests what users and screen readers see, and it breaks when a11y breaks. Fall back to getByLabelText, getByText; getByTestId is the last resort and needs a comment.
  • userEvent, not fireEvent. await user.type(...), await user.click(...) simulate real interaction sequences (focus, keydown, input).
  • findBy* for async appearance, waitFor for async disappearance/assertions. Never setTimeout in tests.
  • Assert on outcomes, not implementation. Don't assert that setState was called; assert the DOM now shows the result.
  • One behaviour per test, named as a sentence: it('shows validation error when email is empty on submit').
  • Don't test the library. No tests that React Hook Form validates, that TanStack Query caches. Test your wiring: the right message appears for your schema.

Template:

it('submits the form and shows confirmation', async () => {
  server.use(http.post('/api/orders', () => HttpResponse.json({ id: '42' })));
  const user = userEvent.setup();
  renderWithProviders(<Checkout />);

  await user.type(screen.getByLabelText(/email/i), 'a@b.com');
  await user.click(screen.getByRole('button', { name: /place order/i }));

  expect(await screen.findByText(/order #42 confirmed/i)).toBeInTheDocument();
});

4. Mocking

  • Network → MSW. Handlers live in src/test/msw/handlers/<feature>.ts, mirror the real API shape, and are reused by Storybook and Playwright. Override per test with server.use(...).
  • Time → vi.useFakeTimers() / vi.setSystemTime(); restore in afterEach.
  • Modules → vi.mock only for true externals (analytics, feature flags, window.matchMedia). Mocking your own modules is a design smell — inject via props or context instead.
  • Never mock useState, useEffect, or the query client.

5. Hooks

  • Test hooks through the component that uses them when possible.
  • For reusable hooks in shared/hooks, use renderHook from Testing Library with a wrapper providing needed context; drive with act and assert result.current.
  • Async hooks: await waitFor(() => expect(result.current.isSuccess).toBe(true)).

6. Storybook as a test layer

  • Every shared/ui primitive has stories for each visual state (default, hover, disabled, error, loading, long text, RTL).
  • Interaction tests via play functions using @storybook/test; run headless in CI with @storybook/test-runner.
  • Add @storybook/addon-a11y and fail CI on violations for the design system.
  • Reuse MSW handlers via msw-storybook-addon so stories render real data flows.

7. End-to-end with Playwright

  • Test journeys, not pages: sign-up → onboarding → first key action; purchase; permission-gated flows.
  • Use page.getByRole locators, test.step for readability, and auth via stored storageState — log in once, not per test.
  • Run against a production build (next build && next start) in CI, not the dev server.
  • Isolate data: each test creates what it needs via API and cleans up, or runs against a seeded ephemeral DB.
  • Retries: retries: 2 in CI only; a test needing retries locally is a bug to fix, not to hide.
  • Trace on first retry (trace: 'on-first-retry') so failures are debuggable from the artifact.

8. CI shape

lint + tsc  →  unit + component (parallel, sharded)  →  build  →  storybook test-runner  →  playwright (sharded, retries)

Fail the pipeline on: type errors, lint errors, test failures, coverage drop below threshold on changed files, a11y violations in Storybook. Don't gate on a global coverage number climbing forever.

9. When asked to "write tests for this component"

  1. Read the component and identify user-visible behaviours (states, transitions, errors, edge cases) — list them first.
  2. Check for existing renderWithProviders and MSW handlers; reuse, don't duplicate.
  3. Write one it per behaviour with role-based queries and userEvent.
  4. Cover: happy path, validation/error path, loading/empty state, keyboard interaction where relevant.
  5. Run the tests. If something needs getByTestId, ask whether the component needs an accessible name instead.

Flaky-test triage

Symptom Likely cause Fix
Passes alone, fails in suite Shared module state, MSW handler not reset resetHandlers, avoid module-level mutable state
Fails on CI only Timing, viewport, timezone Fake timers, set TZ=UTC, fixed viewport
"not wrapped in act(...)" State update after test ended await findBy*, unmount, or await the promise you fired
Random getBy failures Element not yet rendered Use findBy*