Skip to content

Testing React Server Components

React Server Components (RSC) represent a fundamental shift in how React applications render - but they also create new testing challenges. This guide shows you how to effectively test RSC using Scenarist and Playwright.

Why Server Components Need Different Testing

Section titled “Why Server Components Need Different Testing”

Where do Server Components fit in the testing pyramid? They don’t fit neatly into traditional categories:

  • Not isolated units - They fetch data, read cookies, access headers, and depend on server infrastructure
  • Not traditional integration tests - They render UI, not just return data
  • Full E2E is too slow - Spinning up browsers for every scenario combination doesn’t scale
// This FAILS in Jest:
import { render } from '@testing-library/react';
import ProductsPage from './app/products/page';
test('renders products', async () => {
render(<ProductsPage />); // Error: Objects are not valid as a React child (found: [object Promise])
});

Jest and React Testing Library cannot render async Server Components because:

  1. Server Components return Promises - RTL expects synchronous React elements
  2. Server-only APIs - headers(), cookies() from next/headers throw outside Next.js
  3. No browser environment - Server Components have no DOM to render into

From the Next.js Testing Documentation:

“Since async Server Components are new to the React ecosystem, some tools do not fully support them. In the meantime, we recommend using End-to-End Testing over Unit Testing for async components.”

Scenarist + Playwright fills this gap by testing your server-side code as it actually runs - in a real Next.js environment with actual server-side rendering, middleware execution, and session handling:

// This WORKS with Scenarist + Playwright:
import { test, expect } from './fixtures';
test('premium users see discounted pricing', async ({ page, switchScenario }) => {
await switchScenario(page, 'premiumUser');
await page.goto('/products?tier=premium');
// Everything executes: middleware, session checks, RSC data fetching, rendering
await expect(page.getByText('£99.99')).toBeVisible();
});

Testing Server Components with Scenarist gives you capabilities that unit tests cannot provide:

RSC ChallengeUnit TestsScenarist + Playwright
Async component rendering❌ RTL can’t render Promises✅ Full server-side rendering
headers() / cookies() APIs❌ Throw outside Next.js✅ Real Next.js execution
Data fetching in components❌ Must mock fetch globally✅ Real fetch, mocked external APIs
Error boundaries❌ Must mock error conditions✅ Real error propagation

Why this matters for RSC specifically:

  • No mocking Next.js internals - headers(), cookies(), and other server APIs work naturally
  • Real server-side rendering - HTML is generated exactly as in production
  • Actual component composition - Parent/child RSC relationships execute correctly
  • Fast scenario switching - Test many data fetching scenarios without server restarts

Before testing RSC patterns, ensure your setup includes header forwarding. This is critical because Server Components need to forward the test ID header to external APIs.

Server Components use headers() from next/headers, which returns ReadonlyHeaders. Use the dedicated helper:

app/products/page.tsx
import { headers } from 'next/headers';
import { getScenaristHeadersFromReadonlyHeaders } from '@scenarist/nextjs-adapter/app';
export default async function ProductsPage() {
const headersList = await headers();
const response = await fetch('https://api.stripe.com/v1/products', {
headers: {
...getScenaristHeadersFromReadonlyHeaders(headersList), // Forward test ID
'Authorization': `Bearer ${process.env.STRIPE_KEY}`,
},
});
const products = await response.json();
return <ProductList products={products} />;
}

For complete setup instructions, see Getting Started with Next.js App Router.


This guide covers seven patterns for testing React Server Components. Choose based on your testing needs:

PatternPageUse Case
Data FetchingData FetchingBasic RSC data fetching with request matching
Stateful MocksData FetchingShopping carts, state that builds across requests
SequencesData FetchingPolling, retry logic, multi-step workflows
StreamingStreamingSuspense boundaries, progressive loading
AuthenticationInteractionsProtected routes, session handling
Server ActionsInteractionsForm submissions, mutations
Error BoundariesInteractionsError handling and recovery