Testing Strategy — What to Test and at Which Layer

A test suite that tests everything at the same layer is either too slow or too brittle. A test suite with no strategy is a collection of accidents. The testing pyramid provides a simple mental model: many fast, cheap unit tests at the base; a moderate number of integration tests in the middle; a small number of slow, expensive end-to-end tests at the top. For a full-stack FastAPI + React application, this pyramid spans both sides — Python tests on the backend, JavaScript tests on the frontend, and Playwright E2E tests that drive both together.

Full-Stack Testing Pyramid

                    ▲
                   /E\
                  / 2 \  End-to-End: Playwright
                 / E   \  · Critical user flows (login, create post, like)
                /  T    \  · Slow (~30s per test), few (10–20 tests)
               /─────────\  · Run in CI nightly or on release branches
              /           \
             / Integration \ Integration: pytest + React Testing Library
            /               \  · API endpoints with real test DB
           /    ~30–50%      \  · Components with mocked API (MSW)
          /                   \  · Medium speed, moderate count
         /─────────────────────\
        /                       \
       /         Unit            \ Unit: pytest + Vitest
      /           ~50–60%         \  · Pure functions, reducers, validators
     /                             \  · Business logic, schemas
    /                               \  · Fast (<1s), many (100+)
   ─────────────────────────────────

BACKEND TEST LAYERS:
  Unit:         Pydantic validators, utility functions, reducer-style logic
  Integration:  FastAPI endpoints via TestClient with test PostgreSQL
  E2E:          Full HTTP stack via Playwright

FRONTEND TEST LAYERS:
  Unit:         Redux reducers, custom hook logic, utility functions
  Component:    React components via RTL with MSW API mocking
  E2E:          Playwright driving the browser against real backend
Note: The pyramid ratios are guidelines, not rules. For a CRUD-heavy API like the blog application, integration tests (API endpoints with a database) provide the highest value per test because they exercise the most code paths in one test — routing, validation, business logic, database queries, and response serialisation all run together. A 100% unit test suite with no integration tests would need mocks of mocks and would not catch the actual database query bugs that matter most.
Tip: Apply the “test at the lowest sufficient layer” principle. Testing that a slug validator rejects uppercase letters is a unit test — no database, no HTTP, no React needed. Testing that creating a post with an uppercase slug returns a 422 from the API is an integration test — valuable but slower. Running Playwright to verify a user cannot type uppercase slugs in the form is an E2E test — extremely slow and brittle. Unit test the validator; trust that the API and UI wire it up correctly without re-testing the same rule at every layer.
Warning: End-to-end tests are valuable but expensive to maintain. A test that clicks through the full registration → login → create post → verify flow will break when any UI element changes its text, position, or data-testid. Reserve E2E tests for critical happy paths that are expensive to get wrong (payment flows, authentication, the primary user journey). Test edge cases, error states, and variations at the component or integration level where they are faster and cheaper to maintain.

What to Test at Each Layer

Layer Test Do Not Test Here
Backend Unit Pydantic validators, password hashing, slug generation, token expiry logic Database queries, HTTP routing
Backend Integration All API endpoints: status codes, response shape, DB state after mutations, auth guards Pure business logic covered by unit tests
Frontend Unit Redux reducers, custom hooks (with mock), utility functions like normaliseApiError DOM rendering, API calls
Frontend Component PostCard renders correctly, form shows errors, login redirects after success Cross-component navigation flow, backend logic
E2E Register → login → create post → verify in feed, like post → notification appears Edge cases, error messages, individual component behaviour

Common Mistakes

Mistake 1 — E2E testing what unit tests should cover

❌ Wrong — Playwright test that verifies slug validation rules:

Each validation rule tested via Playwright takes ~15 seconds and breaks whenever the UI changes. Test the validator function directly in a 5ms unit test.

✅ Correct — one E2E test verifying the happy path; unit tests for all validation rules.

Mistake 2 — No integration tests (unit tests pass, API is broken)

❌ Wrong — 100% unit test coverage but the SQL queries are wrong:

Unit tests with mocked DB sessions cannot catch incorrect SQL, missing JOIN conditions, or wrong column names.

✅ Correct — integration tests against a real test database catch these issues.

🧠 Test Yourself

You need to test that the normaliseApiError utility returns “slug: value is not a valid string” for a FastAPI 422 response. Which test layer is most appropriate?