Testing React components
Test behaviour the way a user sees it, not implementation details.
- Render a component and query by role, label or text.
- Simulate typing and clicking with userEvent.
- Wait for async content with findBy instead of fixed sleeps.
- Assert on visible, accessible output, not internal state.
In plain English
React Testing Library renders a component into a simulated DOM so you can interact with it the way a user would. Rendering means mounting the component with render, after which you query for elements by role or accessible text. User events such as clicking and typing are simulated with userEvent and are asynchronous. findBy queries wait for content that appears after data loads, and accessible queries are preferred over test IDs.
Step by step
- 1Render the component with render and pass any required props.
- 2Find the element with an accessible query such as getByRole or getByLabelText.
- 3Act with userEvent to simulate real interaction.
- 4Assert on the user-visible result, waiting with findBy when it is async.
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { vi } from 'vitest'
import SearchForm from './SearchForm'
test('submits the typed query', async () => {
const user = userEvent.setup()
const onSearch = vi.fn()
render(<SearchForm onSearch={onSearch} />)
await user.type(screen.getByLabelText('Search lessons'), 'hooks')
await user.click(screen.getByRole('button', { name: 'Search' }))
expect(onSearch).toHaveBeenCalledWith('hooks')
})Common mistakes
- Querying by a CSS class couples the test to styling, which changes without changing behaviour.
- Using waitFor with a fixed delay is slow and flaky compared to a findBy query.
- Forgetting that userEvent is asynchronous means the test finishes before the interaction lands.
- Asserting on internal state instead of visible output gives tests that pass while the UI is broken.
Test a loading list
Write a test that renders a list that loads asynchronously, waits for an item to appear, and asserts it is visible.
import { render, screen } from '@testing-library/react'
import LessonList from './LessonList'
test('shows lessons after loading', async () => {
render(<LessonList />)
})Which query waits for an element to appear asynchronously?
Make it yours.
Keep a private note for this lesson, then turn the key ideas into flashcards you can revisit.
Lock in what you just learned.
Mark this lesson complete to save your place and move one step closer to the certificate.