plugins/react-testing-techniques/skills/react-testing/SKILL.md
This skill should be used when the user asks to "write React tests", "test a component", "set up test utils", "mock an API", "use React Testing Library", "configure MSW", "test async behavior", "choose query type", "test Mantine components", "test a modal", or when writing tests for React components using Vitest, RTL, MSW, or Mantine. Provides best practices for component and integration testing.
npx skillsauth add mrclrchtr/skills react-testingInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Best practices for testing React components with Vitest, React Testing Library (RTL), and Mock Service Worker (MSW).
Write tests that interact with components the way users do. Avoid testing implementation details.
userEvent, not fireEventCombine multiple components and use MSW for API mocking at the network level. Avoid mocking component internals, hooks, or services unless absolutely necessary.
Choose the right query based on the scenario:
| Scenario | Query | Why |
|----------|-------|-----|
| Element exists synchronously | getBy* | Throws if not found - fails fast |
| Element appears after async operation | findBy* | Waits with retry, use with await |
| Assert element does NOT exist | queryBy* | Returns null instead of throwing |
| Multiple elements expected | *AllBy* variants | Returns array of matches |
Priority order for queries:
getByRole - most accessible, preferredgetByLabelText - for form fieldsgetByPlaceholderText - for inputsgetByText - for non-interactive contentgetByTestId - last resort onlyAlways use userEvent.setup() before interactions:
import { render, screen } from '../test/test-utils';
import userEvent from '@testing-library/user-event';
test('submits form correctly', async () => {
const user = userEvent.setup();
render(<MyForm />);
await user.type(screen.getByRole('textbox', { name: /email/i }), '[email protected]');
await user.click(screen.getByRole('button', { name: /submit/i }));
expect(await screen.findByText(/success/i)).toBeInTheDocument();
});
Never use fireEvent - it dispatches raw DOM events without realistic user interaction simulation.
findBy* for Elements That Appear Async// Element appears after data loads
expect(await screen.findByText('John Doe')).toBeInTheDocument();
waitFor for Assertions Only// Wait for a side effect or state change
await waitFor(() => expect(handleSubmit).toHaveBeenCalledTimes(1));
waitForElementToBeRemoved for Disappearing Elements// Wait for loading spinner to disappear
await waitForElementToBeRemoved(() => screen.getByText('Loading...'));
Use Mock Service Worker for API mocking. Always use v2 syntax:
import { http, HttpResponse } from 'msw';
import { setupServer } from 'msw/node';
const server = setupServer(
http.get('/api/users', () => {
return HttpResponse.json([{ id: 1, name: 'John' }]);
})
);
beforeAll(() => server.listen());
afterEach(() => server.resetHandlers());
afterAll(() => server.close());
Override handlers per test:
test('handles error state', async () => {
server.use(
http.get('/api/users', () => {
return HttpResponse.json({ error: 'Not found' }, { status: 404 });
})
);
// Test error handling...
});
Use Vitest APIs, not Jest:
| Operation | Vitest | Not Jest |
|-----------|--------|----------|
| Mock function | vi.fn() | ~~jest.fn()~~ |
| Mock module | vi.mock() | ~~jest.mock()~~ |
| Spy on method | vi.spyOn() | ~~jest.spyOn()~~ |
| Stub env var | vi.stubEnv() | ~~process.env~~ |
| Reset modules | vi.resetModules() | ~~jest.resetModules()~~ |
| Restore mocks | vi.restoreAllMocks() | ~~jest.restoreAllMocks()~~ |
Environment variable testing:
import { afterEach, vi } from 'vitest';
afterEach(() => {
vi.unstubAllEnvs();
vi.resetModules();
});
test('handles missing env var', async () => {
vi.stubEnv('API_KEY', '');
const { config } = await import('./config');
expect(() => config.apiKey).toThrow();
});
Create a custom render with all providers. Read references/test-utils-setup.md for complete setup with:
env="test")Except for trivial components (<30 lines of output). Snapshots encode structure, not intent.
Use MSW for APIs instead of mocking fetch/axios. Mock only at system boundaries.
Consult these for detailed patterns:
references/test-utils-setup.md - Complete custom render setup with providersreferences/query-selection.md - Detailed query type decision guidereferences/vitest-patterns.md - Vitest mocking, spying, and stubbingreferences/msw-patterns.md - MSW v2 handlers, overrides, error simulationreferences/async-testing.md - waitFor, findBy, timing patternsreferences/tanstack-testing.md - TanStack Query and Router testingreferences/user-interactions.md - userEvent patterns and edge casesreferences/mantine-testing.md - Mantine component testing (modals, forms, select)development
Check for available upgrades to the pi coding agent framework by comparing the current `@earendil-works/pi-*` or legacy `@mariozechner/pi-*` version in package.json against releases on `earendil-works/pi`. Use this skill whenever the user asks to upgrade pi, update pi, check pi changelogs/releases, or migrate off the deprecated `@mariozechner/*` packages.
development
You MUST use this before creative OpenSpec work - creating features, building components, adding functionality, or modifying behavior. Explores user intent, requirements and design before implementation, then hands off to the best matching OpenSpec skill or command without managing OpenSpec artifacts.
development
Fetches and normalizes http(s) web pages into clean Markdown for LLM ingestion. Use when a task includes a URL, needs to fetch docs or asks to convert web docs/articles/pages into Markdown for summarizing, quoting, diffing, or saving.
development
Download Stitch (stitch.withgoogle.com) screen screenshots at best quality from `screenshot.downloadUrl` (often `lh3.googleusercontent.com`). Use to normalize googleusercontent size parameters from canvas dimensions, download with curl, optionally verify pixel dimensions, and avoid committing signed URLs.