Developer Experience6 min read

React Testing Library

Test React components with React Testing Library - user-centric queries, async interactions, mocking, and accessibility-aware testing.

React Testing LibraryTestingJestVitestReact

skill: React Testing Library category: Developer Experience tags: [React Testing Library, Testing, Jest, Vitest, React] readingTime: 6

Test the component as the user sees it - not the implementation details. RTL's queries (getByRole, getByText) mirror how assistive technologies access the DOM. If a test is hard to write, it's often a signal the component is too complex - refactor the component, not the test.

Workflow

  1. Query by role and text, not by classRTL's getByRole mirrors how screen readers find elements - tests written this way break when accessibility regresses, not when CSS class names change.
  2. Simulate user interactions with userEventuserEvent simulates real browser events (mousedown, keydown, focus, change) - more realistic than fireEvent which fires single events.
  3. Handle async UI with waitForComponents that fetch data show loading states before rendering content. waitFor/findBy waits for async state updates.
  4. Mock API calls with MSWMock Service Worker intercepts network requests at the service worker level - more realistic than mocking fetch directly.
  5. Test custom hooksCustom hooks can be tested in isolation with renderHook - faster than wrapping in a component.

Output Format

RTL tests: getByRole queries, userEvent interactions, findBy for async, MSW for API mocking, and renderHook for custom hooks.

const user = userEvent.setup();
render(<Component />);
await user.click(screen.getByRole('button', { name: /submit/i }));
expect(await screen.findByText(/success/i)).toBeInTheDocument();

Examples

Input

Output

Edge Cases & Gotchas

  • getBy vs. queryBy vs. findBy - getBy throws if not found (for elements that must exist). queryBy returns null (for asserting absence). findBy returns a Promise (for async elements).
  • act() warnings - Warning "not wrapped in act()" means state updates happen after the test finishes. Wrap the async action in await act(async () => { ... }) or use findBy which wraps automatically.
  • Context providers in tests - Components that use React Router, Redux, or custom contexts fail without wrapping. Create a custom render() function that wraps with all providers.
  • Flaky animation tests - CSS transitions cause timing-sensitive failures. Disable animations in tests: .mockImplementation(() => {}) on requestAnimationFrame or use jest.useFakeTimers().

When to Break These Rules

  • Use Playwright or Cypress for E2E tests that cover full user journeys across pages - RTL tests are component-scoped.
  • Use Storybook interaction tests for component testing within the visual development environment.

Reference Files

  • https://testing-library.com/docs/react-testing-library/intro
  • https://mswjs.io/

More skills in this library

Browse all technical skills - Mobile, AI/ML, Web, Backend, Design, and Product.