Concept
Testing Behavior, Not Implementation
Prior testing tools (like Enzyme) focused on component internals (e.g. state keys or mock class internals).
React Testing Library (RTL) uses a user-centric philosophy: test the component the way a real user interacts with it. It does this by rendering components into a virtual DOM (via jsdom) and querying elements using accessible tags (like ARIA roles, text contents, or labels).
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { Counter } from './Counter';
test('increments counter on button click', async () => {
render(<Counter />);
// Query element by its accessible ARIA role
const button = screen.getByRole('button', { name: /increment/i });
const countText = screen.getByText(/count: 0/i);
expect(countText).toBeInTheDocument();
// Simulate real user interaction (handles hover, focus, click events)
await userEvent.click(button);
expect(screen.getByText(/count: 1/i)).toBeInTheDocument();
});Screen Queries Priority
RTL queries should follow a strict priority checklist to match accessibility guidelines:
- Queries Accessible to Everyone:
getByRole(button, link, heading).getByLabelText(form inputs).getByPlaceholderText(fallback form inputs).getByText(buttons, divs, spans).
- Semantic Queries:
getByAltText(image alt tags).- (svg/iframe titles).
Handling Asynchronous Updates
When operations fetch API data or update state asynchronously, UI elements appear after a delay. RTL handles this using findBy queries or waitFor blocks:
test('loads data asynchronously', async () => {
render(<UserCard />);
// findBy queries await elements internally up to 1000ms
const userName = await screen.findByText(/ada lovelace/i);
expect(userName).toBeInTheDocument();
});Common Mistakes
1. Using fireEvent instead of userEvent
fireEvent dispatches raw browser events directly without simulating browser lifecycle behaviors. userEvent is the correct method because it simulates real user steps (hovering, focusing, pressing down, releasing key keys), triggering all associated event listeners.
2. Wrapping assertions in unnecessary act() calls
React Testing Library wraps rendering and user-event triggers inside act() internally. Explicitly wrapping every click or expect statement in act() produces warning console errors and makes code verbose. Only use act() when testing custom asynchronous hook flows directly.
Best Practices
- Prioritize getByRole: Search for elements using their ARIA roles to ensure your components are accessible to screen readers.
- Avoid querying container elements: Never use
document.querySelector('.btn')inside RTL tests; this accesses internal classes, breaking test isolation. - Use user-event setup: Initialize
userEvent.setup()at the beginning of your test blocks rather than calling it statically:const user = userEvent.setup(); await user.click(button);
