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”The Testing Gap
Section titled “The Testing Gap”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
The Jest Problem
Section titled “The Jest Problem”// 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:
- Server Components return Promises - RTL expects synchronous React elements
- Server-only APIs -
headers(),cookies()fromnext/headersthrow outside Next.js - 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.”
The Scenarist Solution
Section titled “The Scenarist Solution”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();});What This Enables for RSC
Section titled “What This Enables for RSC”Testing Server Components with Scenarist gives you capabilities that unit tests cannot provide:
| RSC Challenge | Unit Tests | Scenarist + 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
Setup Requirements for App Router
Section titled “Setup Requirements for App Router”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.
Header Forwarding in Server Components
Section titled “Header Forwarding in Server Components”Server Components use headers() from next/headers, which returns ReadonlyHeaders. Use the dedicated helper:
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.
RSC Testing Patterns
Section titled “RSC Testing Patterns”This guide covers seven patterns for testing React Server Components. Choose based on your testing needs:
Pattern Overview
Section titled “Pattern Overview”| Pattern | Page | Use Case |
|---|---|---|
| Data Fetching | Data Fetching | Basic RSC data fetching with request matching |
| Stateful Mocks | Data Fetching | Shopping carts, state that builds across requests |
| Sequences | Data Fetching | Polling, retry logic, multi-step workflows |
| Streaming | Streaming | Suspense boundaries, progressive loading |
| Authentication | Interactions | Protected routes, session handling |
| Server Actions | Interactions | Form submissions, mutations |
| Error Boundaries | Interactions | Error handling and recovery |
Next Steps
Section titled “Next Steps”- Data Fetching Patterns - Start here for core RSC testing patterns
- Next.js App Router Getting Started - Complete setup guide
- Example App - Full working example with all patterns