How I test Server Components, API routes, and client interactions in Next.js App Router. Real examples from SculptQL and Hoop Almanac—not documentation, actual patterns that work.
Testing in Next.js App Router is different from Pages Router. Server Components can't use hooks. Server Actions need different mocking strategies. Here's how I actually test my projects—not theory, real patterns.
Pages Router: Everything was client-side by default. You could test with React Testing Library, mock fetch, done. App Router: Server Components run on the server. You can't render them directly in Jest/Vitest without setup. Client Components need 'use client' directive.
I split tests into three categories: 1) Unit tests for pure functions and utilities, 2) Component tests for Client Components, 3) Integration tests for full page flows with Playwright.
In Hoop Almanac, the fantasy scoring calculations are pure functions. They don't care about React. I test them with Vitest directly. The XGBoost prediction formatter, the draft pick validator, the trade analyzer—all pure functions, all easy to test.
Example: The scoring function takes player stats and league settings, returns fantasy points. No React, no server, just math. Test input, assert output.
In SculptQL, the query editor is a Client Component (CodeMirror needs browser APIs). I test it with React Testing Library. The key insight: mock the heavy dependencies, test the interactions.
I don't test that CodeMirror renders correctly—that's CodeMirror's job. I test that MY code responds correctly: when user types a query, does the state update? When they click execute, does the handler fire?
Controversial take: I don't unit test Server Components. Here's why. A Server Component's job is to fetch data and pass it to children. The data fetching is tested via API tests. The children are Client Components I can test. The Server Component itself is just glue.
Instead, I use Playwright for integration tests. Load the actual page, verify the content appears. This tests the full stack: Server Component fetches data, passes to Client Component, renders correctly.
In OpportunIQ, the AI diagnosis endpoint is a Route Handler. I test it by importing the handler function directly and passing mock Request objects. No need to spin up a server.
The pattern: create a Request with the expected body, call the handler, assert on the Response. Mock external services (OpenAI) with MSW or manual mocks.
Server Actions are trickier. They're async functions that run on the server but are called from the client. In my portfolio, the contact form uses a Server Action.
My approach: extract the business logic into a separate function. The Server Action becomes a thin wrapper that handles form data and calls the logic. Test the logic function directly, integration test the full form flow.
Vitest for unit tests (faster than Jest, native ESM). React Testing Library for Client Components. Playwright for E2E and integration tests. MSW for API mocking when needed.
If you're migrating: 1) Your utility function tests stay the same. 2) Component tests need 'use client' awareness—mock or skip Server Components. 3) API route tests change syntax (Route Handlers vs API Routes) but the pattern is similar. 4) Add Playwright for confidence in the full stack.
Don't fight the framework. Server Components are hard to unit test because they're not meant to be unit tested. Test the pieces that make sense to test in isolation, use integration tests for the full picture. Ship with confidence, not 100% coverage.