Skip to content
fearchitect
Quality, Observability & Cross-Cutting

Frontend Testing Strategy

Test what the user sees, not how the code is wired.

By Abas TurabliReviewed

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.

Jump to the interview angle

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.

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 APItsx
// 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(<UserCard userId="1" />);

  // 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(<UserCard userId="1" />);
  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.

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.

Interview angle

Name the testing trophy and explain why integration tests dominate. Mention MSW v2 http.get for network mocking, Testing Library's role-based queries, and Playwright for E2E.

Soundbite: "Test behavior from the user's perspective — query by role, intercept the network with MSW, and treat a flaky test as a bug, not a skip."

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.

Further reading

Search fearchitect

Jump to a topic, mode, or action.