React Forum
Blog & Guide Responses
Oct 08, 2022 · 04:00 AM

React Testing Library Intro - discussion (2022-10-08)

Started by Ari Novak
externaltestingrtluicontractsdebugging

We’re trying to upgrade our unit/integration tests so they actually protect the route story instead of asserting random DOM trivia.

Read the docs

What do you consider a good “unit” test boundary in a React app (component, route shell, behavior slice)? Do you have conventions for making tests narratable (rendered markers, stable contract keys) without turning everything into data-testid soup? What patterns have reduced flaky tests the most for you?

Comments (10)

Back to latest
Maya Henson
Oct 08, 2022 · 04:06 AM

The best tests read like user behavior. The worst tests read like “this div exists because I stared at the DOM once”.

Dmitri Kline
Oct 08, 2022 · 04:14 AM

If you want tests that survive refactors, you need a stable vocabulary in the UI.

We render small contract markers (lane/posture/version) and assert on those instead of brittle structure.

Related: Testing and Debugging and Performance and Rendering Best Practices.

Salma Qureshi
Oct 08, 2022 · 04:22 AM

A pattern that helped us: assert the contract marker, then assert one user-visible outcome.

tsx
render(<CheckoutRoute />);
expect(screen.getByTestId('court')).toHaveAttribute('data-court', 'dual');
await user.type(screen.getByRole('textbox', { name: /email/i }), 'a@b.com');
expect(screen.getByText(/valid/i)).toBeInTheDocument();
Keira Santos
Oct 08, 2022 · 04:31 AM

Counterpoint: “no testids ever” is a religion that makes tests worse.

If you have an internal contract marker you actually *want* to assert on, a data-* attribute is fine.

Ari Novak
Oct 08, 2022 · 04:36 AM

Agreed. Our compromise: roles/labels for user intent, and a small number of explicit markers for route posture.

What we avoid is data-testid on every nested span just to satisfy a brittle test.

Keira Santos
Oct 08, 2022 · 04:39 AM

Yep. A few honest markers beat a thousand accidental ones.

Hana Saito
Oct 08, 2022 · 04:47 AM

Flake killer: stop waiting for arbitrary timeouts and start waiting for a visible state transition. If the route has a posture marker, you can wait for data-status='ready' instead of sleeping.

tsx
render(<SearchRoute />);
await waitFor(() => expect(screen.getByTestId('route')).toHaveAttribute('data-status', 'ready'));
expect(screen.getByRole('heading', { name: /results/i })).toBeInTheDocument();
Camille Duarte
Oct 08, 2022 · 04:56 AM

We also stopped mocking everything. Mocking made tests “fast” but it detached them from the actual route story.

A couple bigger tests with real wiring were worth more than 200 tiny mocks.

Noah Vance
Oct 08, 2022 · 05:07 AM

Our best unit boundary is usually “component + its contract props”.

If the component relies on a global, we treat that as a smell and either inject it or test at the route boundary instead.

Priya Liao
Oct 08, 2022 · 05:21 AM

A lot of flake came from effects that settle state after render.

Once we deleted those and stored derived evidence up front, tests stopped racing hidden timing.

Inez Park
Oct 08, 2022 · 05:32 AM

When a test fails, I want a screenshot to be enough. If nothing on screen can prove posture/state, debugging is pure guesswork.

Benji Rios
Oct 08, 2022 · 05:44 AM

One rule: tests should assert on behavior *and* the contract marker that explains the behavior.

Otherwise you’ll keep writing tests that pass for the wrong reasons.