Next.js

Verified against Claude Code · 2026-07-25

Write a test strategy for App Router code that doesn't pretend Server Components unit-test like hooks

Separates what App Router code is actually directly testable — Route Handlers, Server Actions — from what genuinely needs an integration or end-to-end approach, instead of forcing everything through the same unit-test pattern.

Claude CodeCursorGitHub Copilot4 fillable variables

The prompt

Ready to copy — highlighted parts are example details you can swap.

You are designing a test strategy for a piece of App Router code, and the first job is classifying what kind of test that specific code actually supports, rather than defaulting to the same unit-test pattern used for client-side hooks and components regardless of what's actually being tested.

CODE TO TEST
app/api/orders/route.ts (POST handler that validates and creates an order) and app/orders/[id]/page.tsx (an async Server Component that fetches and renders order details)

CODE TYPE
The route.ts file is a Route Handler; the page.tsx file is a Server Component

EXISTING TEST SETUP
Vitest for unit tests, Playwright already configured for a small number of existing e2e flows

KEY BEHAVIORS TO COVER
POST /api/orders returns 400 on an invalid payload, 201 with the created order on success; the order detail page shows a not-found state for a deleted or inaccessible order id

TESTING RULES
If this is a Route Handler, test it directly and cheaply: import the exported GET, POST, or other method function and call it with a real, constructed Request object, then assert on the returned Response's status and body — this needs no browser, no rendering, and no test server, since a Route Handler is just an async function that takes a Request and returns a Response, and testing it as exactly that is both the simplest and the most accurate representation of what it actually does in production. If this is a Server Action, test it the same way: it's a plain async function once you have a reference to it, so call it directly with constructed FormData or arguments and assert on its returned value, mocking only the actual external dependency named below, such as a database client or an external API call — do not reach for a browser-based test to exercise a Server Action's own logic when a direct function call tests the identical code path with far less setup and far less flakiness. If this is a Server Component, be honest about the real limitation: it's an async function that can't be rendered synchronously the way React Testing Library renders a client component, and there's no first-party, widely adopted pattern for unit-rendering an async Server Component the way there is for a client one — the realistic options are testing the plain data-fetching and transformation logic it calls as ordinary functions, separate from the JSX it returns, or verifying the actual rendered output through an integration or end-to-end tool like Playwright that runs a real browser against a real running instance of the app; recommend whichever fits the specific behavior being verified, and say plainly if the honest answer is that a given behavior is better covered by end-to-end testing than forced into a unit test that would only prove the function didn't throw. Mock only at the actual external boundary — the database client, the third-party API, the mailer — never by reaching into the framework's own internals (a Request/Response implementation, Next.js's own caching layer) to fake behavior that a real Request/Response object already provides correctly and for free. Cover both the success path and every named failure mode explicitly — a Route Handler test suite that only exercises a 200 response has not actually tested the validation-failure or not-found branches that live in the same function, even though those branches are just as reachable as the success path in real traffic.

OUTPUT FORMAT
For each piece of code listed, state which category it falls into (directly testable, or needs integration/e2e) and why, then the actual test code for whatever is directly testable, and a one-line note on what the untestable-at-the-unit-level parts should be verified with instead.

Customize

Optional — swap in your own details for the highlighted parts above.

Why this works

A Route Handler is, structurally, just an async function that accepts a Request and returns a Response — nothing about testing it correctly requires a browser, a rendering step, or a running server, and treating it as exactly what it is lets a test call the exported function directly with a real, constructed Request object and assert on the real returned Response, which is both simpler to write and a more accurate representation of what actually happens in production than any test double for the framework's routing layer could be. Server Actions share the same underlying reality once the reference to the function exists: they're plain async functions, and testing them by calling them directly with constructed input tests the identical code path a real form submission would exercise, without needing to render a form, simulate a click, or wait on any of the DOM machinery that a component-level test would otherwise require just to reach a function this directly callable. The honest treatment of Server Components is the part a generic testing prompt tends to paper over, and it matters precisely because pretending otherwise wastes real engineering time: there is no equivalent of React Testing Library's synchronous render for an async Server Component, so a testing strategy that insists on unit-testing one anyway typically ends up either mocking away everything interesting about what the component does, which proves nothing beyond "the function didn't throw," or fighting the tooling to force a pattern it wasn't built for — separating the component's own data-fetching and transformation logic (genuinely unit-testable as plain functions) from its actual rendered output (better verified through integration or end-to-end testing against a real running app) gives each part a testing approach that actually tells you something true, instead of one approach stretched to cover both badly. Requiring both the success and failure paths as explicit, separately-named coverage targets closes the common gap where a Route Handler test suite exercises only the 200 response and calls the handler tested, even though the validation-failure and not-found branches live in the exact same function and are exactly as reachable by real traffic — a suite that never calls the handler with a deliberately invalid payload has not actually verified that the 400 path returns a 400 at all, it's simply never been asked to.

What you get back

describe('POST /api/orders', () => { it('returns 400 with field errors on an invalid payload', async () => { const req = new Request('http://localhost/api/orders', { method: 'POST', body: JSON.stringify({ items: [] }) }); const res = await POST(req); expect(res.status).toBe(400); expect((await res.json()).errors.items).toBeDefined(); });

it('returns 201 with the created order on a valid payload', async () => { const req = new Request('http://localhost/api/orders', { method: 'POST', body: JSON.stringify({ items: [{ sku: 'ABC', qty: 1 }] }) }); const res = await POST(req); expect(res.status).toBe(201); }); });

app/orders/[id]/page.tsx: category is integration/e2e, not unit-testable directly as an async Server Component. Recommend extracting getOrder(id) as its own tested function for the fetch/transform logic, and covering the actual "shows a not-found state for a deleted order" behavior with a Playwright test that navigates to a real deleted order's URL against a running instance and asserts on the rendered not-found UI.

Verified against

Claude Code Sonnet 4.6 · 2026-07-25

Cursor Cursor 2.1 · 2026-08-02

Changelog

  • 2026-07-25 — Initial publish, verified against Claude Code (Sonnet 4.6) and Cursor 2.1 on a Next.js 16 app with Vitest for Route Handlers and Playwright for Server Component pages.

Need this built into your business?

If a prompt isn't enough — custom software, built and maintained for you — that's Scult's day job.

EXPLORE CUSTOM SOFTWARE
All Next.js prompts

Check your AI visibility

One URL in, a 0–100 score and the exact fixes out.

RUN THE CHECK

Browse all the tools

15 tools across six categories
13 of them never send your data anywhere

Free · No signup · No trial clock

SEE THE DIRECTORY