Overview
Frontend tests have a bad reputation because the wrong ones were written for years: snapshot dumps nobody reads, shallow renders asserting implementation details, E2E suites that fail when the CSS changes. The modern consensus inverts the pyramid into Kent C. Dodds's trophy: lean on integration-level component tests that interact with the UI the way a user does, mock the network at the HTTP boundary, keep a thin E2E layer for the flows that pay the bills, and let visual tools cover what assertions can't. The unit of confidence is a user-visible behavior, never a component's internals.
Key points
- Test what the user sees, not how it's built: Testing Library's guiding principle —
query by role/label/text (
getByRole('button', {name: /save/i})), interact viauser-event, assert on the DOM. If a refactor that preserves behavior breaks the test, the test was wrong. - The trophy, not the pyramid: static (TypeScript + lint) at the base, few pure-unit tests (logic extracted from components), a thick middle of component/integration tests, a thin crown of E2E. UI logic lives at the integration level; that's where the return on confidence is.
- Mock the network, not your modules: MSW intercepts at the HTTP layer, so the whole data path (client, cache, serialization) stays real and the same handlers serve tests, Storybook, and local dev. Mocking your own API client couples tests to implementation.
- E2E with Playwright for the money paths only: signup, login, checkout, the core loop — real browser, real backend where feasible, auto-waiting assertions, trace viewer for the failures. Every E2E test is an ongoing operational cost; budget them.
- What assertions can't catch, tools can: visual regression (Chromatic/Percy or
Playwright screenshots) for the pixels,
jest-axe/axe-corein component tests plus keyboard-walk E2E for accessibility — automated a11y catches the ~30–40% that is machine-checkable, no more. - Flakiness is a design smell, not weather: no arbitrary
sleep, await visible outcomes, isolate test data, control the clock; a retried-until-green suite is a suite nobody trusts — same discipline as testing strategies at large.
Details
The shape in code
test("saves the article and shows it in the feed", async () => {
server.use(http.post("/api/articles", () => HttpResponse.json(article)));
render(<Editor />, { wrapper: AppProviders });
await user.type(screen.getByRole("textbox", { name: /title/i }), "CQRS in anger");
await user.click(screen.getByRole("button", { name: /publish/i }));
expect(await screen.findByRole("heading", { name: /cqrs in anger/i })).toBeVisible();
});
One test: real component tree, real query cache, real router, fake HTTP. This is the trophy's thick middle in one frame.
Tooling map
Vitest (runner, jsdom or browser mode) + Testing Library (queries/interactions) + MSW (network) for the middle; Playwright for E2E and screenshots; Storybook stories reused as test cases (portable stories / play functions) so docs and tests stop diverging.
Practice
- Test the design system (source) — add
behavior tests +
jest-axeto the components from the design-system build; per-state stories become the test cases. - MSW retrofit (source) — take a fetch-heavy feature and write integration tests with MSW handlers; then break the real API contract on purpose and watch which tests catch it (and which lie).
- Playwright money-path suite (source) — pick the three flows a real product would die without, automate them with trace-on-failure, wire them into CI as the merge gate.
- RealWorld full-stack build (source) — milestone 3 is this note as a deliverable: trophy-shaped suite, axe clean, E2E in CI.
Related
- Testing strategies — the general strategy this note specializes to the UI.
- Component architecture & state management — behavior-first tests are what make that architecture refactorable.
- Accessibility — the a11y checks that belong inside every component test.
- Design systems — Storybook as the shared surface of docs and tests.
- CI/CD & delivery engineering — where these suites gate the pipeline.
- Hexagonal reference implementation — Frontend — the house architecture whose test strategy (domain in Node, components in a real browser against domain fakes) leans on these practices.