This guide shows how to query elements with React Testing Library, the same way a user or a screen reader would. Queries find elements after a component renders, and choosing the right one keeps tests tied to real behavior instead of markup.
The three query families
Every query has a single form and a plural form, and the prefix changes what happens when nothing matches.
| Prefix | No match | More than one match | Async |
|---|---|---|---|
| getBy | throws | throws | no |
| queryBy | returns null | throws | no |
| findBy | throws | throws | yes |
| getAllBy | throws | returns array | no |
| queryAllBy | returns empty array | returns array | no |
| findAllBy | throws | returns array | yes |
Use getBy for elements that must be there, queryBy only when you are asserting absence, and findBy when the element appears after async work. If you are new to the library, start with the React Testing Library tutorial. The findBy family is covered with async component testing.
Query by role first
The getByRole query matches elements exposed in the accessibility tree. A name option filters by accessible name, which usually comes from text content, a label, or an aria label.
function Toolbar() {
return <button type="button">Save changes</button>;
}The test finds the button by its role and its visible name, which is exactly how a user and a screen reader locate it.
import { render, screen } from "@testing-library/react";
import Toolbar from "./Toolbar";
test("finds the save button by its role", () => {
render(<Toolbar />);
expect(screen.getByRole("button", { name: "Save changes" })).toBeEnabled();
});If getByRole cannot find an element, the UI is often missing an accessible name. The better fix is to improve the component rather than switch to a weaker query.
Form fields use labels
Form controls should be found through their labels, because that is how users navigate forms.
function SignUp() {
return (
<form>
<label htmlFor="email">Email</label>
<input id="email" type="email" />
</form>
);
}The getByLabelText query matches the label that is associated with the control through the htmlFor and id pair, exactly as a browser would resolve it.
import { render, screen } from "@testing-library/react";
import SignUp from "./SignUp";
test("finds the email input by label", () => {
render(<SignUp />);
expect(screen.getByLabelText("Email")).toBeRequired();
});The visible result is a found input, and the assertion confirms the field is required.
Text and display value
Outside forms, visible text is the main way users locate content. The getByText query finds headings, paragraphs, and buttons by their text, while getByDisplayValue finds a control by its current value.
test("shows the current status", () => {
render(<Status value="Signed in" />);
expect(screen.getByText("Signed in")).toBeVisible();
});Text matching is exact and case-sensitive by default. Pass a regular expression for partial matches, which expresses intent more clearly than a loose string match. The visible text is what a user reads, so it is the most direct target for an assertion.
Scope with within
The screen object queries the whole document. When a page has many repeated elements, scope the query to one section with the within helper.
import { render, screen, within } from "@testing-library/react";
test("reads the count inside one card", () => {
render(<Dashboard />);
const card = screen.getByRole("region", { name: "Revenue" });
expect(within(card).getByText("$1,240")).toBeVisible();
});The within helper binds queries to the Revenue region, so the text lookup only sees that card. The test does not collide with another card showing the same number.
Test IDs are the last resort
The getByTestId query matches data-testid attributes. Users cannot see or hear these, so use them only when no role, label, or text makes sense.
test("finds a loading spinner", () => {
render(<Uploader />);
expect(screen.getByTestId("upload-spinner")).toBeInTheDocument();
});A spinner has no meaningful text, so a test ID is a reasonable contract here. Reserve it for cases like this, and prefer a real accessible status message when one exists.
Common mistakes
- Using queryBy where getBy would fail louder and catch a real regression.
- Using getByTestId for a button or input that already has an accessible name.
- Forgetting the within helper and matching an element from the wrong section.
- Waiting with getBy when the element arrives after a network response.
The next natural step is performing the interactions that follow a query. See how to test user events in React.
Rune AI
Key Insights
- Prefer getByRole, then getByLabelText, then text and display value queries.
- getBy throws on no match, queryBy returns null, findBy retries asynchronously.
- Use queryBy only when asserting an element is absent.
- Use within to scope queries to one section of the page.
- Use getByTestId only as an escape hatch.
Frequently Asked Questions
Which query should I use first?
When should I use queryBy instead of getBy?
Conclusion
Query the rendered DOM the way a user perceives it. Prefer roles and labels, scope with within, and reserve test IDs for cases no accessible query can reach.
More in this topic
How to Build a Dropdown Menu in React
Build a React dropdown menu with the ARIA menu button pattern. Handle open and close, keyboard arrows, and clicks outside the menu.
How to Animate React Components with Motion
Animate React components with the Motion library. Set up motion, add enter, hover, and exit animations, and respect reduced motion.
Headless UI Components Explained: Logic Without Locked Styling
Understand headless UI components and how libraries like Radix give you unstyled, accessible behavior that you style yourself.