# Frontend Testing Strategy > Test what the user sees, not how the code is wired. Frontend architecture guidance from fearchitect, written by Abas Turabli and last reviewed 2026-06-21. Source: https://fearchitect.com/topics/frontend-testing Use it as reference for the task at hand. Before changing code, check this guidance against the codebase: where the code already makes a different, deliberate choice, flag the conflict instead of rewriting it. Library APIs move faster than this guide, so confirm exact signatures in the official docs linked at the end. ## Summary Senior engineers test behavior, not implementation. Testing Library queries the DOM the way a user would; MSW intercepts network requests at the service-worker layer so you never mock `fetch` directly. The modern testing trophy weights integration tests heavily over unit tests, and Playwright covers the E2E and component-testing layers with a single API. ## Testing philosophy The golden rule (from Kent C. Dodds): **the more your tests resemble the way your software is used, the more confidence they give**. That means querying by role, label, and text — not by CSS class or component internals. Unit tests that pin implementation details break on safe refactors, eroding trust in the suite without catching real bugs. The **testing trophy** (not pyramid) reflects this: a thin layer of static analysis at the base, a large band of integration tests in the middle, and a smaller E2E layer at the top. Isolated unit tests are the smallest slice. ## Diagram ```mermaid graph TD E2E["E2E — Playwright (few)"] INT["Integration — RTL + MSW (most)"] UNIT["Unit — pure logic (few)"] STATIC["Static — TypeScript + ESLint (always on)"] E2E --> INT --> UNIT --> STATIC ``` The testing trophy: integration tests deliver the best ROI — they test real behavior with low maintenance cost. ## Integration test with Testing Library and MSW v2 MSW v2 uses `http.get` (not `rest.get`). The handler intercepts the browser's actual fetch; no module mock needed. Queries use `getByRole` and `findByText` — never `data-testid` for things users can perceive. **UserCard renders name from API** ```tsx // tests/UserCard.test.tsx import { render, screen } from "@testing-library/react"; import userEvent from "@testing-library/user-event"; import { http, HttpResponse } from "msw"; import { server } from "@/mocks/server"; // MSW server setup import { UserCard } from "@/components/UserCard"; test("shows user name after load", async () => { server.use( http.get("/api/user/:id", () => HttpResponse.json({ id: "1", name: "Ada Lovelace" }) ) ); render(); // findBy* is async — awaits the network round-trip expect(await screen.findByText("Ada Lovelace")).toBeInTheDocument(); }); test("shows error state on 500", async () => { server.use( http.get("/api/user/:id", () => HttpResponse.json({ error: "server error" }, { status: 500 }) ) ); render(); expect(await screen.findByRole("alert")).toBeInTheDocument(); }); ``` `http.get` from MSW v2 replaces the old `rest.get`. `HttpResponse.json` sets status and body. The component's real fetch fires; no `vi.mock` on `fetch` or `axios`. ## What to test — and what to skip - Test observable outputs: text rendered, ARIA roles present, navigation triggered. - Test all network states: loading, success, error, empty — MSW handles each with `server.use`. - Test user flows end-to-end with Playwright when you need real navigation and cookies. - Skip testing library internals (React state, private methods, implementation details). - Skip snapshot tests of large component trees — they break on any change and give no signal. ## Flaky-test discipline - Never `await sleep(n)` — always await a condition: `findByText`, `waitFor`, or Playwright's auto-wait. - Each test owns its server handlers via `server.use` — reset in `afterEach` with `server.resetHandlers()`. - Playwright tests: use `page.getByRole` and `expect(locator).toBeVisible()` over XPath selectors. - Quarantine a flaky test in one PR, diagnose the race before re-enabling — never disable and forget. ## Watch out: Never mock fetch directly Replacing `global.fetch` with a `vi.fn()` tests your mock, not your code. MSW intercepts at the network layer — the same code path runs in tests and production. Swap the handler, not the transport. ## Key terms - **Testing trophy**: A weighting model prioritizing integration tests over unit tests for the best confidence-to-maintenance ratio. - **Testing Library**: A DOM query library that forces tests to interact through accessible roles, labels, and text — not selectors. - **MSW (Mock Service Worker)**: A network interception library using a Service Worker; tests fire real fetch calls, handlers return fake responses. - **Playwright component testing**: Playwright's `@playwright/experimental-ct-react` mounts a single component in a real browser for E2E-grade accuracy. - **server.use**: MSW v2 API to override a handler for one test; `server.resetHandlers()` restores defaults after each test. ## Related topics - [Frontend Observability](https://fearchitect.com/topics/frontend-observability.md): Capture errors, measure real-user performance, and trace what breaks in production. - [Error Boundaries & Resilience](https://fearchitect.com/topics/error-boundaries-resilience.md): Isolate render failures so one widget can't crash the page. - [CI/CD for Frontend](https://fearchitect.com/topics/ci-cd-frontend.md): Automated pipeline from commit to production with quality gates. - [AI-Assisted Dev Workflow](https://fearchitect.com/topics/ai-assisted-dev-workflow.md): Using AI coding agents well without handing them ownership. - [Accessibility (a11y)](https://fearchitect.com/topics/accessibility.md): WCAG 2.2 AA: semantic HTML, keyboard nav, and ARIA done right. - [Internationalization (i18n)](https://fearchitect.com/topics/internationalization.md): Ship UI that works correctly in any locale without code changes. ## Further reading - [Testing Library — Guiding principles](https://testing-library.com/docs/guiding-principles) - [MSW v2 — Getting started](https://mswjs.io/docs/getting-started) - [MSW v2 — http namespace (replaces rest)](https://mswjs.io/docs/api/http) - [Playwright — Component testing](https://playwright.dev/docs/test-components) - [Kent C. Dodds — Write tests, not too many, mostly integration](https://kentcdodds.com/blog/write-tests)