HT
How Things Work
System Online

Jest & React Testing Library

Testing React components the way users experience them — Jest as the runner, RTL's accessible queries and userEvent, async findBy*, and asserting behavior over internals.

How It Works

Jest is the test runner (assertions, mocking, snapshots, coverage); React Testing Library is the philosophy and toolkit for testing components through the DOM the way users actually use them. Together they encourage behavior-focused, accessibility-first tests — query by role/label/text, interact with userEvent, and assert on rendered output — producing tests that survive refactors and give real confidence.

1
Jest: The Test Runner

Jest runs the tests and provides the scaffolding: describe/it blocks, the expect assertion API and matchers, mocking (jest.mock, jest.fn), fake timers, snapshots, and coverage. It executes tests in isolated environments (jsdom for components) and reports results — the engine RTL plugs into.

2
RTL: Test What Users See

React Testing Library's guiding principle: 'the more your tests resemble the way your software is used, the more confidence they give you.' Instead of poking at component internals (state, props, instance methods), you render the component and interact with the actual DOM — exactly as a user would.

3
Accessible Queries

RTL pushes you to find elements by accessibility-first queries — getByRole, getByLabelText, getByText — with getByTestId as a last resort. These mirror how users and assistive tech locate things, so your tests are resilient to markup/refactor changes and double as a light accessibility check.

4
userEvent & Async

userEvent simulates realistic interactions (full click/type sequences with focus and key events), which is more faithful than firing raw events. For asynchronous UI, findBy* queries and waitFor auto-retry until the element appears, so tests handle data fetching and transitions without arbitrary sleeps.

Key Concepts

🃏Jest

The test runner: describe/it, expect matchers, mocking, fake timers, snapshots, and coverage.

🧑‍ðŸ’ŧTesting Library

Render components and test via the DOM the way users interact — not via internals.

â™ŋAccessible Queries

getByRole/getByLabelText/getByText find elements as users and screen readers do; getByTestId is a fallback.

ðŸ–ąïļuserEvent

Simulates realistic user interactions (click, type, tab) more faithfully than raw fireEvent.

âģfindBy / waitFor

Async queries that auto-wait for elements to appear — for data fetching and transitions.

ðŸŽŊTest Behavior, Not Internals

Assert on rendered output, not state/props, so refactors don't break correct tests.

RTL component test
tsx
1import { render, screen } from "@testing-library/react";
2import userEvent from "@testing-library/user-event";
3
4test("increments the count when the button is clicked", async () => {
5 const user = userEvent.setup();
6 render(<Counter />);
7
8 // Query the way a USER (or screen reader) finds things — by role/label/text,
9 // NOT by test-only ids or internal component state.
10 const button = screen.getByRole("button", { name: /increment/i });
11 await user.click(button);
12 await user.click(button);
13
14 // Assert on the rendered OUTPUT, not on state/props internals.
15 expect(screen.getByText("Count: 2")).toBeInTheDocument();
16});
17
18// Async UI: findBy* queries auto-wait for the element to appear.
19test("shows results after fetch", async () => {
20 render(<Search query="cats" />);
21 expect(await screen.findByText(/3 results/i)).toBeVisible();
22});
ðŸ’Ą
Why This Matters

Jest + RTL is the de facto standard for testing React, and it's expected knowledge for frontend roles. Its core lesson — test behavior through the DOM, not implementation details — is what makes a component suite durable and trustworthy instead of a brittle obstacle to refactoring.

Common Pitfalls

⚠Asserting on internal state/props or calling instance methods — brittle tests that break on refactor.
⚠Overusing getByTestId instead of accessible role/label queries (missing the a11y benefit and resilience).
⚠Using getBy for async content that isn't there yet — use findBy/waitFor instead.
⚠Snapshot tests for everything — large snapshots get blindly updated and stop catching real regressions.
⚠fireEvent for complex interactions where userEvent better mirrors real user behavior (focus, key sequences).
Real-World Use Cases

1Tests Coupled to Component Internals

Scenario

An older component suite (Enzyme-style) asserted on internal state and called instance methods directly. A refactor from class to hooks — with identical user-facing behavior — broke nearly every test.

Problem

The tests verified HOW the component worked internally, not WHAT the user sees. They were tightly coupled to implementation details, so a behavior-preserving refactor caused mass false failures and blocked modernization.

Solution

Rewrite with React Testing Library: render the component, interact via userEvent, and assert on the rendered DOM (getByRole/getByText). The tests no longer know or care whether it's a class or hooks — they pass as long as the user-visible behavior is unchanged.

ðŸ’Ą

Takeaway: Test components through the DOM as users experience them, not via internal state/methods. Behavior-focused tests survive refactors and actually verify what matters: what the user sees.

2Flaky Async Component Test

Scenario

A component fetches data on mount and renders a list. The test asserted the list 'immediately' after render and failed intermittently because the fetch hadn't resolved yet.

Problem

The test didn't account for asynchronous rendering. Using a synchronous getBy query (or a fixed delay) raced the data fetch, producing non-deterministic pass/fail.

Solution

Use the async query: expect(await screen.findByText(/3 results/)).toBeVisible(). findBy* polls until the element appears (or times out), so the test waits exactly as long as the fetch needs — no arbitrary sleeps, no flakiness.

ðŸ’Ą

Takeaway: For async UI, use findBy*/waitFor instead of synchronous queries or fixed delays. Auto-waiting queries make data-fetching components testable deterministically.

Join the discussion

Comments

?

Loading comments...

Cookie preferences

We use essential cookies for sign-in and preferences. With your permission, we also use basic analytics to improve lessons.