# 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)