Testing UI with Testing Library
Test user-facing behavior — queries by role and label, async utilities, and what not to assert.
intermediate1 min read
- testing
- react
- a11y
The guiding principle from Testing Library: test software the way users use it. Prefer roles, labels, and text over CSS selectors and internal state.
Query priority
getByRole— buttons, links, headings, dialogsgetByLabelText/getByPlaceholderText— form fieldsgetByText— non-interactive contentgetByTestId— last resort
import { render, screen } from '@testing-library/react';
import userEvent from '@testing-library/user-event';
import { LoginForm } from './LoginForm';
test('submits email and password', async () => {
const user = userEvent.setup();
const onSubmit = vi.fn();
render(<LoginForm onSubmit={onSubmit} />);
await user.type(screen.getByLabelText(/email/i), 'a@b.com');
await user.type(screen.getByLabelText(/password/i), 'secret');
await user.click(screen.getByRole('button', { name: /sign in/i }));
expect(onSubmit).toHaveBeenCalledWith({
email: 'a@b.com',
password: 'secret',
});
});
If getByRole cannot find a control, your UI may have an accessibility bug, not just a test problem.
Async
expect(await screen.findByRole('alert')).toHaveTextContent(/saved/i);
Use findBy* for content that appears after fetch. Prefer waitFor sparingly when asserting non-element conditions.
What not to do
- Snapshot entire pages as the only test
- Assert on class names that are design-system internals
- Test implementation details (
useStatevalues) - Mock so much that the test only verifies the mock
Coverage of failure paths
Good suites include empty, loading, error, and unauthorized states — the same states your design system should define.
Related guides
- Accessibility Testing in CIWire axe and lint rules into CI without false confidence — what automation catches, what it misses, and a practical pipeline.
- Component Testing StorybookUse Storybook as a component workshop and optional test runner — stories as living specs, interaction tests, and a11y checks.
- Contract Testing APIsKeep frontend and backend agreements honest — schema contracts, Pact-style consumer tests, and OpenAPI-driven checks without brittle E2E.
- Coverage Metrics PitfallsWhy 100% line coverage can still ship bugs — gaming metrics, useless tests, and what coverage is actually good for.
- End-to-End Testing TradeoffsWhen E2E tests earn their keep, why they flake, and how to keep a small critical-path suite instead of a slow second frontend.