React Testing Library Patterns
Quick Guide: Test React components through user interactions and accessible queries. UsegetByRoleas your primary query. PreferuserEventoverfireEvent. UsefindBy*for async content. Test user behavior, not implementation details.
<critical_requirements>
CRITICAL: Before Using This Skill
All code must follow project conventions in CLAUDE.md (kebab-case, named exports, import ordering, import type, named constants)
(You MUST use the query priority hierarchy: getByRole > getByLabelText > getByText > getByTestId)
(You MUST use userEvent instead of fireEvent for realistic user interactions)
**(You MUST use findBy* queries for async content instead of waitFor + getBy*)**
(You MUST test user-visible behavior, NOT implementation details like internal state)
(You MUST use screen object for queries, NOT destructured render returns)
</critical_requirements>
Auto-detection: React Testing Library, @testing-library/react, render, screen, userEvent, fireEvent, waitFor, findBy, getByRole, getByLabelText, renderHook, within, cleanup, prettyDOM, configure, logRoles, logTestingPlaygroundURL
When to use:
- Testing React component behavior through user interactions
- Verifying accessible element rendering and presence
- Testing form interactions, validation, and submissions
- Testing async component states (loading, error, success)
- Testing custom React hooks with
renderHook - Creating custom render functions with providers
When NOT to use:
- E2E testing spanning multiple pages (defer to your E2E testing tool)
- Network request mocking setup (defer to your API mocking solution)
- Testing pure utility functions without React (use unit tests directly)
- Test runner configuration (defer to your test runner skill)
Key patterns covered:
- Query hierarchy and selection strategy
- userEvent vs fireEvent for user simulation
- Async utilities (waitFor, findBy queries)
- Testing hooks with renderHook
- Custom render with providers
- Accessibility testing patterns
- Debug utilities (screen.debug, prettyDOM, logRoles)
- Scoped queries with
within - Global configuration options
Detailed Resources:
- For code examples, see
examples/folder: - [examples/core.md](examples/core.md) - Query hierarchy examples
- [examples/user-events.md](examples/user-events.md) - userEvent patterns
- [examples/async-testing.md](examples/async-testing.md) - findBy, waitFor, waitForElementToBeRemoved
- [examples/custom-render.md](examples/custom-render.md) - Custom render with providers
- [examples/hooks.md](examples/hooks.md) - renderHook patterns
- [examples/accessibility.md](examples/accessibility.md) - Accessibility testing patterns
- [examples/scoped-queries.md](examples/scoped-queries.md) - within() for scoped queries
- [examples/configuration.md](examples/configuration.md) - Global configuration options
- For decision frameworks and anti-patterns, see [reference.md](reference.md)
<philosophy>
Philosophy
React Testing Library is built on the guiding principle: "The more your tests resemble the way your software is used, the more confidence they can give you."
Core Principles:
- Test User Behavior, Not Implementation: Query elements the way users find them (by role, label, text), not by test IDs or CSS selectors
- Accessibility-First Testing: If your test struggles to find an element, your UI likely has accessibility issues
- No Implementation Details: Avoid testing internal state, refs, or component internals - test what users see and interact with
- Real DOM Interactions: Render components to a real DOM (jsdom) to catch real integration issues
When to use React Testing Library:
- Integration testing components with their children
- Testing user interaction flows within a component
- Verifying accessibility of interactive elements
- Testing form validation and submission
- Testing conditional rendering based on props/state
When NOT to use:
- Full user journey testing (use E2E tests)
- Testing visual appearance (use visual regression tools)
- Testing network requests directly (test component behavior with mocked responses)
- Testing third-party library behavior (trust the library's tests)
</philosophy>
<patterns>
Core Patterns
Pattern 1: Query Priority Hierarchy
Select queries based on accessibility hierarchy. This ensures tests align with how users (including those using assistive technology) interact with your UI.
Query Priority Order
// Priority 1: Accessible to Everyone
getByRole(); // BEST - queries accessibility tree
getByLabelText(); // Form fields - how users navigate forms
getByPlaceholderText(); // When no label (not ideal, but sometimes necessary)
getByText(); // Non-interactive content (divs, spans, paragraphs)
getByDisplayValue(); // Form elements by current value
// Priority 2: Semantic Queries
getByAltText(); // Images, areas, inputs with alt
getByTitle(); // Least reliable - not consistently read by screen readers
// Priority 3: Test IDs (Last Resort)
getByTestId(); // Only when other methods fail
See [examples/core.md](examples/core.md) for complete query examples.
Why this hierarchy: Users interact with your app through visible text, labels, and semantic roles - not through test IDs or CSS classes. Testing this way ensures your app is accessible.
Pattern 2: userEvent Over fireEvent
Use userEvent for realistic user interaction simulation. It triggers the full event chain that real interactions produce.
Key Differences
| Action | fireEvent | userEvent | | -------- | --------------------- | ----------------------------------------------------------- | | Typing | Single change event | keyDown, keyPress, keyUp per character | | Clicking | Single click event | pointerDown, mouseDown, pointerUp, mouseUp, click | | Focus | Manual management | Automatic focus management |
Setup Pattern (userEvent v14+)
import userEvent from "@testing-library/user-event";
// Setup BEFORE interactions - creates isolated user session
const user = userEvent.setup();
// Then use throughout test
await user.click(button);
await user.type(input, "Hello");
See [examples/user-events.md](examples/user-events.md) for complete userEvent examples.
Why userEvent: fireEvent dispatches DOM events directly, bypassing browser event handling. userEvent simulates actual user behavior, triggering the complete event chain including focus, keyboard, and pointer events.
Pattern 3: Async Utilities
Use findBy* queries for elements that appear asynchronously. Use waitFor only for assertions, not element queries.
findBy vs waitFor
// GOOD: findBy for async elements
const button = await screen.findByRole("button", { name: /submit/i });
// BAD: waitFor + getBy for async elements
await waitFor(() => {
screen.getByRole("button", { name: /submit/i }); // DON'T DO THIS
});
waitFor Best Practices
// GOOD: Single assertion in waitFor
await waitFor(() => {
expect(screen.getByText(/success/i)).toBeInTheDocument();
});
// BAD: Multiple assertions in waitFor
await waitFor(() => {
expect(screen.getByText(/success/i)).toBeInTheDocument();
expect(screen.getByText(/complete/i)).toBeInTheDocument(); // DON'T
});
// BAD: Side effects in waitFor
await waitFor(() => {
user.click(button); // DON'T - side effects outside waitFor
expect(result).toBe(true);
});
See [examples/async-testing.md](examples/async-testing.md) for complete async testing examples.
Why this matters: waitFor polls until the callback stops throwing. Multiple assertions or side effects in the callback cause unpredictable behavior and slower tes