Testing Library: components like a user
Queries by role and label, get/query/find, user-event.
Updated
What Testing Library is
React Testing Library (RTL) is a library for testing components the way a person uses them: you find elements by what you see (text, label, role), you click, you type — and you check what shows up on screen. You don't look at internal state or props.
Its principle: "The more your tests resemble the way your software is used, the more confidence they can give you."
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
test('adds a todo', async () => {
const user = userEvent.setup()
render(<TodoApp />)
await user.type(screen.getByLabelText('New task'), 'Learn RTL')
await user.click(screen.getByRole('button', { name: 'Add' }))
expect(screen.getByText('Learn RTL')).toBeInTheDocument()
})The queries — in order of priority
Pick the first one on the list that works. The order reflects how accessible the component is:
| Priority | Query | Finds by | Example |
|---|---|---|---|
| 1 | getByRole |
role + accessible name | getByRole('button', { name: 'Save' }) |
| 2 | getByLabelText |
a field's label | getByLabelText('Email') |
| 3 | getByPlaceholderText |
the placeholder | if there's no label |
| 4 | getByText |
the visible text | paragraphs, messages |
| 5 | getByDisplayValue |
an input's value | pre-filled forms |
| 6 | getByAltText / getByTitle |
alt / title |
images |
| last | getByTestId |
data-testid |
only when nothing else works |
A bonus: if you can't find an element with getByRole, most of the time your component has an accessibility problem. Tests push you toward better HTML.
The variants: get, query, find
| Prefix | If it doesn't find it | Async | When |
|---|---|---|---|
getBy |
throws an error | no | the element must exist now |
queryBy |
returns null |
no | checking that it does not exist: expect(queryByText('Error')).toBeNull() |
findBy |
throws after a timeout | yes | the element appears later (after a fetch, a setTimeout) |
getAllBy... / queryAllBy... / findAllBy... for several elements.
user-event vs fireEvent
userEvent |
fireEvent |
|
|---|---|---|
| Simulates | the full interaction (focus, keydown, input, keyup, click) | a single DOM event |
| Realism | high | low |
| When | the default | special cases |
What not to do
- Testing internal state (
component.state.count). container.querySelector('.btn-primary')— classes are implementation details.- Huge snapshots that nobody reads.
Summary
- RTL tests components like a user: you find by role, label, text.
- Priority:
getByRole→getByLabelText→ … →getByTestId. get= must exist,query= may be missing,find= will appear.