React Testing Library provides the render and query utilities that pair with Jest to test React components the way a real user would experience them. This lesson covers its core API surface in depth.
render(), screen, and Queries
render(<Component />) mounts a component into a detached DOM node managed by jsdom. screen then gives you query methods (getByText, getByRole, getByLabelText, and more) to find elements within that rendered output, without needing to manually track container references.
Queries come in three flavors: getBy... (throws if not found — for elements that should exist), queryBy... (returns null if not found — for asserting absence), and findBy... (returns a promise — for elements that appear asynchronously).
import { render, screen } from '@testing-library/react';
import { LoginForm } from './LoginForm';
test('shows a validation error for an empty email', async () => {
render(<LoginForm />);
screen.getByRole('button', { name: /sign in/i }).click();
expect(await screen.findByText('Email is required')).toBeVisible();
});
findByText waits for the validation message to appear, since it likely renders after an async state update.
getBy vs queryBy vs findBy
screen.getByText('Save'); // throws if not found
screen.queryByText('Error'); // returns null if not found
await screen.findByText('Loaded'); // waits, then throws or resolves
Use getBy... when the element should already be present; a missing element is a real test failure.
Use queryBy... combined with .not.toBeInTheDocument() to assert something is absent.
Use findBy... for elements that appear after an async action (data fetching, animations).
Prefer accessible queries (getByRole, getByLabelText) over getByTestId when possible.
React Testing Library Cheat Sheet
The queries and utilities used in nearly every RTL test.
API
Purpose
render(<Component />)
Mounts a component into a test DOM
screen.getByRole('button', { name })
Finds an element by accessibility role
screen.getByText('...')
Finds an element by visible text
screen.queryByText('...')
Finds an element, or returns null
await screen.findByText('...')
Waits for an element to appear
userEvent.click(el) / .type(el, text)
Simulates realistic user interaction
expect(el).toBeVisible()
jest-dom matcher for visibility
user-event vs. fireEvent
fireEvent dispatches a single, low-level DOM event (like a raw click). @testing-library/user-event simulates a fuller sequence of real browser events for the same interaction (pointer down, focus, click, pointer up), which more accurately reflects what happens when a real user interacts with the page — especially important for form inputs and complex components.
import userEvent from '@testing-library/user-event';
test('submits the form with typed values', async () => {
const user = userEvent.setup();
render(<LoginForm />);
await user.type(screen.getByLabelText('Email'), 'ada@example.com');
await user.click(screen.getByRole('button', { name: /sign in/i }));
expect(await screen.findByText('Welcome back!')).toBeVisible();
});
user-event is asynchronous and more realistic; it's the currently recommended default over fireEvent.
Query Priority: Accessible Queries First
Testing Library's own documentation recommends a priority order for queries, favoring ones that match how real users and assistive technology perceive the page: role, label text, placeholder text, text content, and only falling back to data-testid when nothing else reasonably applies.
1. getByRole — matches how screen readers perceive elements.
2. getByLabelText — ideal for form fields.
3. getByText — for non-interactive visible content.
4. getByTestId — last resort, when no accessible query fits.
Common Mistakes
Reaching for getByTestId first instead of trying accessible queries like getByRole.
Using fireEvent for complex interactions where user-event would behave more realistically.
Using getBy... for elements expected to be absent, causing a thrown error instead of a clean assertion.
Forgetting await before findBy... queries, or before user-event interactions.
Key Takeaways
render() and screen are the foundation of every React Testing Library test.
getBy, queryBy, and findBy serve different purposes: expected present, expected absent, and expected eventually.
user-event simulates realistic interaction sequences more accurately than fireEvent.
Prefer accessible queries (getByRole, getByLabelText) over data-testid whenever possible.
Pro Tip
Run screen.debug() inside a failing test to print the current rendered DOM to your terminal — it's the fastest way to see exactly what Testing Library is (or isn't) finding.
You now know React Testing Library's core APIs. Next, explore testing Vue components with Jest.