Next.js Unit-Tests in der Praxis: Vollständiger Jest + React Testing Library Konfigurationsguide

Montag um zehn schrieb der Tech Lead in die Gruppe: „Ab dieser Woche Unit-Tests fürs Projekt – Next.js mit Jest.“
React-Tests kannte ich, aber App Router und Server Components? Keine Ahnung, wie man die testet. npm install jest, npm test – und der Bildschirm explodierte in Rot:
Error: Cannot use import statement outside a module
SyntaxError: Unexpected token 'export'
Cannot find module 'next/navigation'
Zwei Tage zwischen Konfigurationsdateien, Stack Overflow und GitHub Issues. Dutzende Konfigurationsversuche – als die Tests endlich grün liefen, wollte ich die Tastatur hochwerfen.
Die Next.js-Testumgebung ist komplexer als gedacht, aber nicht mystisch: wenige Kernkonfigurationen, typische Fallstricke vermeiden – in zehn Minuten läuft es. Dieser Artikel führt Sie durch Next.js 15 + Jest + React Testing Library, teilt unsere Stolpersteine und zeigt Tests für Client Components, Server Components, Hooks und API-Mocks.
Testumgebung einrichten (von null auf eins)
Zuerst die Umgebung zum Laufen bringen. Die Konfiguration wirkt trocken – jede Option wird erklärt, damit Copy-Paste nicht ins Leere geht.
Abhängigkeiten installieren
Im Terminal alles auf einmal installieren:
npm install -D jest jest-environment-jsdom @testing-library/react @testing-library/dom @testing-library/jest-dom ts-node @types/jest
Kurz zu den Paketen:
- jest: Test-Framework
- jest-environment-jsdom: simuliert Browser-DOM (für React-Komponenten)
- @testing-library/react: React-Komponenten-Tests
- @testing-library/jest-dom: zusätzliche Assertions (z. B.
toBeInTheDocument()) - ts-node und @types/jest: TypeScript-Unterstützung (bei JS optional)
Nach der Installation noch nicht testen – die Konfiguration fehlt.
jest.config.ts erstellen
Die zentrale Konfigurationsdatei. Im Projektroot jest.config.ts anlegen (bei JS: .js):
import type { Config } from 'jest'
import nextJest from 'next/jest'
// Lädt automatisch die Next.js-Konfiguration
const createJestConfig = nextJest({
dir: './', // Next.js-Projektroot
})
const config: Config = {
coverageProvider: 'v8', // Code-Coverage-Tool
testEnvironment: 'jsdom', // Browser-Umgebung simulieren
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'], // Setup vor Tests
}
// createJestConfig umschließt die Konfiguration und übernimmt Next.js-Transformationen
export default createJestConfig(config)
Warum next/jest?
next/jest übernimmt automatisch:
.css,.module.css(Mock, sonst Fehler in Tests)- Bilder, Fonts und statische Assets
.env-Umgebungsvariablen- TypeScript- und JSX-Transformation
- Ausschluss von
node_modulesund.next
Ohne das alles manuell konfigurieren – das tut weh.
jest.setup.ts erstellen
Im Root jest.setup.ts anlegen:
import '@testing-library/jest-dom'
Importiert Jest-DOM-Matcher für Assertions wie:
expect(element).toBeInTheDocument()expect(element).toHaveClass('active')expect(element).toBeVisible()
Ohne diese Datei kennt Jest die Methoden nicht.
Pfad-Aliase konfigurieren (bei @/)
Bei Pfad-Alias wie import Button from '@/components/Button' muss Jest die Auflösung kennen.
In tsconfig.json (oder jsconfig.json):
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/components/*": ["components/*"],
"@/lib/*": ["lib/*"]
}
}
}
In jest.config.ts moduleNameMapper ergänzen:
const config: Config = {
coverageProvider: 'v8',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
moduleNameMapper: {
'^@/components/(.*)$': '<rootDir>/components/$1',
'^@/lib/(.*)$': '<rootDir>/lib/$1',
},
}
Warum? Jest kennt @/ standardmäßig nicht – nur relative oder absolute Pfade. Sie sagen Jest: „Bei @/components/Button suche unter <rootDir>/components/Button.“
Test-Skripte hinzufügen
In package.json:
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch"
}
}
npm test: einmal alle Testsnpm test:watch: bei Dateiänderungen automatisch neu testen
Konfiguration prüfen
Einfachen Test anlegen: __tests__/example.test.ts:
describe('Example Test', () => {
it('should pass', () => {
expect(1 + 1).toBe(2)
})
})
npm test ausführen. Grünes PASS und 1 passed – Konfiguration steht.
Bei Fehlern: Abschnitt „Häufige Probleme“ – 90 % der Lösungen dort.
Komponenten-Tests in der Praxis
Konfiguration steht – jetzt echte Tests. Komponenten-Tests sind das Herz von Next.js-Tests; Client und Server Components unterscheiden sich deutlich.
Client Components testen (Standardablauf)
Client Components mit 'use client' sind vertraut und direkt testbar.
Beispiel LoginForm.tsx:
'use client'
import { useState } from 'react'
export default function LoginForm() {
const [email, setEmail] = useState('')
const [error, setError] = useState('')
const handleSubmit = (e: React.FormEvent) => {
e.preventDefault()
if (!email.includes('@')) {
setError('请输入有效的邮箱')
}
}
return (
<form onSubmit={handleSubmit}>
<input
type="email"
value={email}
onChange={(e) => setEmail(e.target.value)}
placeholder="邮箱"
/>
{error && <span role="alert">{error}</span>}
<button type="submit">登录</button>
</form>
)
}
Testdatei LoginForm.test.tsx:
import { render, screen, fireEvent } from '@testing-library/react'
import LoginForm from '@/components/LoginForm'
describe('LoginForm', () => {
it('should render input and button', () => {
render(<LoginForm />)
const emailInput = screen.getByPlaceholderText('邮箱')
expect(emailInput).toBeInTheDocument()
const submitButton = screen.getByRole('button', { name: '登录' })
expect(submitButton).toBeInTheDocument()
})
it('should show error for invalid email', () => {
render(<LoginForm />)
const emailInput = screen.getByPlaceholderText('邮箱')
const submitButton = screen.getByRole('button', { name: '登录' })
fireEvent.change(emailInput, { target: { value: 'invalid-email' } })
fireEvent.click(submitButton)
const errorMessage = screen.getByRole('alert')
expect(errorMessage).toHaveTextContent('请输入有效的邮箱')
})
})
Testablauf:
render()rendert die Komponentescreen.getByXxx()findet Elemente (Rolle, Text, Placeholder)fireEventsimuliert Nutzeraktionenexpect()prüft Ergebnisse
Tipp: lieber getByRole als getByTestId – Rollen wie button oder alert entsprechen dem, was Nutzer sehen; Tests bleiben stabiler.
Server Components testen (etwas unbequem)
Server Components sind zentral in Next.js 15 – Jest unterstützt sie nur eingeschränkt.
Kernproblem: Jest unterstützt keine async Server Components.
Beispiel mit Datenbankabruf:
// app/posts/page.tsx (Server Component)
async function getPosts() {
const res = await fetch('https://api.example.com/posts')
return res.json()
}
export default async function PostsPage() {
const posts = await getPosts()
return (
<ul>
{posts.map(post => (
<li key={post.id}>{post.title}</li>
))}
</ul>
)
}
Direkter Test → Fehler: Objects are not valid as a React child.
Was tun?
Drei Ansätze:
Option 1: Geschäftslogik auslagern, Pure Functions testen
// lib/posts.ts
export async function getPosts() {
const res = await fetch('https://api.example.com/posts')
if (!res.ok) throw new Error('Failed to fetch')
return res.json()
}
// lib/posts.test.ts
import { getPosts } from './posts'
describe('getPosts', () => {
it('should fetch posts successfully', async () => {
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve([{ id: 1, title: 'Test Post' }]),
})
) as jest.Mock
const posts = await getPosts()
expect(posts).toHaveLength(1)
expect(posts[0].title).toBe('Test Post')
})
})
Sie testen den Datenabruf, nicht die Komponente. Komplexe async Logik separat – die Komponente rendert nur noch einfach.
Option 2: synchrone Server Components testen
Ohne async normal testbar:
// components/Title.tsx (Server Component, kein async)
export default function Title({ text }: { text: string }) {
return <h1 className="title">{text}</h1>
}
// components/Title.test.tsx
import { render, screen } from '@testing-library/react'
import Title from './Title'
describe('Title', () => {
it('should render title', () => {
render(<Title text="Hello World" />)
const heading = screen.getByRole('heading', { level: 1 })
expect(heading).toHaveTextContent('Hello World')
})
})
Option 3: E2E-Tests ergänzen
Für komplexe Server Components: Playwright oder Cypress. Jest für Frontend-Logik, E2E für den Gesamtflow.
Mein Ansatz: Kernlogik als Pure Functions, Server Components nur einfaches Rendering, E2E für den Rest.
Interaktions-Tests
@testing-library/react bietet viele Hilfsmittel:
import { render, screen, fireEvent, waitFor } from '@testing-library/react'
// Klick
fireEvent.click(button)
// Eingabe
fireEvent.change(input, { target: { value: 'test' } })
// auf async Update warten
await waitFor(() => {
expect(screen.getByText('Success')).toBeInTheDocument()
})
// Sichtbarkeit
expect(element).toBeVisible()
// Klassen
expect(element).toHaveClass('active')
Vollständiges async Interaktionsbeispiel:
it('should submit form successfully', async () => {
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ success: true }),
})
) as jest.Mock
render(<LoginForm />)
const emailInput = screen.getByPlaceholderText('邮箱')
const submitButton = screen.getByRole('button', { name: '登录' })
fireEvent.change(emailInput, { target: { value: '[email protected]' } })
fireEvent.click(submitButton)
await waitFor(() => {
expect(screen.getByText('登录成功')).toBeInTheDocument()
})
})
waitFor wartet auf async Abschluss. Bei useEffect oder async State-Updates unverzichtbar – sonst läuft die Assertion vor dem Update und liefert Fehlalarme.
Hook-Test-Strategien
Custom Hooks sind React-Kern – wie testen? Manche testen im Komponentenkontext und vermischen Hook- und UI-Logik. Besser: renderHook.
Einfache Hooks testen
Beispiel Zähler-Hook:
// hooks/useCounter.ts
import { useState } from 'react'
export function useCounter(initialValue = 0) {
const [count, setCount] = useState(initialValue)
const increment = () => setCount(c => c + 1)
const decrement = () => setCount(c => c - 1)
const reset = () => setCount(initialValue)
return { count, increment, decrement, reset }
}
Test:
// hooks/useCounter.test.ts
import { renderHook, act } from '@testing-library/react'
import { useCounter } from './useCounter'
describe('useCounter', () => {
it('should initialize with default value', () => {
const { result } = renderHook(() => useCounter())
expect(result.current.count).toBe(0)
})
it('should initialize with custom value', () => {
const { result } = renderHook(() => useCounter(10))
expect(result.current.count).toBe(10)
})
it('should increment count', () => {
const { result } = renderHook(() => useCounter())
act(() => {
result.current.increment()
})
expect(result.current.count).toBe(1)
})
it('should reset count', () => {
const { result } = renderHook(() => useCounter(5))
act(() => {
result.current.increment()
result.current.increment()
})
expect(result.current.count).toBe(7)
act(() => {
result.current.reset()
})
expect(result.current.count).toBe(5)
})
})
Wichtig:
renderHookrendert den Hookactumschließt State-Updates (React-Regel: Update abwarten, dann asserten)result.currentliefert den Hook-Rückgabewert
Hooks mit Context testen
Bei Context-Abhängigkeit (z. B. Auth) brauchen Sie einen Provider.
// hooks/useAuth.ts
import { useContext } from 'react'
import { AuthContext } from '@/contexts/AuthContext'
export function useAuth() {
const context = useContext(AuthContext)
if (!context) {
throw new Error('useAuth must be used within AuthProvider')
}
return context
}
Mit Mock-Provider testen:
// hooks/useAuth.test.tsx
import { renderHook } from '@testing-library/react'
import { useAuth } from './useAuth'
import { AuthContext } from '@/contexts/AuthContext'
describe('useAuth', () => {
it('should return auth context value', () => {
const mockAuthValue = {
user: { id: 1, name: 'Test User' },
login: jest.fn(),
logout: jest.fn(),
}
const wrapper = ({ children }: { children: React.ReactNode }) => (
<AuthContext.Provider value={mockAuthValue}>
{children}
</AuthContext.Provider>
)
const { result } = renderHook(() => useAuth(), { wrapper })
expect(result.current.user).toEqual({ id: 1, name: 'Test User' })
expect(result.current.login).toBeDefined()
})
it('should throw error when used outside provider', () => {
const { result } = renderHook(() => useAuth())
expect(result.error).toEqual(
Error('useAuth must be used within AuthProvider')
)
})
})
Tipp: wrapper mit Provider – dann greift der Hook auf den Context zu.
Async Hooks testen (Datenabruf)
// hooks/useFetch.ts
import { useState, useEffect } from 'react'
export function useFetch<T>(url: string) {
const [data, setData] = useState<T | null>(null)
const [loading, setLoading] = useState(true)
const [error, setError] = useState<Error | null>(null)
useEffect(() => {
const fetchData = async () => {
try {
setLoading(true)
const response = await fetch(url)
if (!response.ok) throw new Error('Network error')
const json = await response.json()
setData(json)
} catch (err) {
setError(err as Error)
} finally {
setLoading(false)
}
}
fetchData()
}, [url])
return { data, loading, error }
}
fetch mocken und auf State-Update warten:
// hooks/useFetch.test.ts
import { renderHook, waitFor } from '@testing-library/react'
import { useFetch } from './useFetch'
describe('useFetch', () => {
beforeEach(() => {
jest.resetAllMocks()
})
it('should fetch data successfully', async () => {
const mockData = { id: 1, title: 'Test' }
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve(mockData),
})
) as jest.Mock
const { result } = renderHook(() => useFetch('/api/data'))
expect(result.current.loading).toBe(true)
expect(result.current.data).toBeNull()
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
expect(result.current.data).toEqual(mockData)
expect(result.current.error).toBeNull()
})
it('should handle fetch error', async () => {
global.fetch = jest.fn(() =>
Promise.resolve({
ok: false,
})
) as jest.Mock
const { result } = renderHook(() => useFetch('/api/data'))
await waitFor(() => {
expect(result.current.loading).toBe(false)
})
expect(result.current.error).toBeTruthy()
expect(result.current.error?.message).toBe('Network error')
expect(result.current.data).toBeNull()
})
})
Hinweise:
waitForfür asyncbeforeEach: Mocks zurücksetzen- Erfolg und Fehler testen
Hook-Tests: Abhängigkeiten mocken, State ändern, Ergebnis prüfen – drei Schritte für jeden Hook.
Mock-Techniken im Überblick
Mocks sind die Seele von Tests. Ohne Mock hängen Tests an echten APIs, DBs und Diensten – langsam und instabil. Next.js braucht spezielle Mocks – hier die wichtigsten.
Next.js-Routing mocken (am häufigsten)
Routing-Hooks (useRouter, usePathname, useSearchParams) sind in Tests standardmäßig nicht verfügbar.
useRouter mocken (App Router):
// __mocks__/next/navigation.ts
export const useRouter = jest.fn()
export const usePathname = jest.fn()
export const useSearchParams = jest.fn()
In der Testdatei:
import { useRouter } from 'next/navigation'
jest.mock('next/navigation', () => ({
useRouter: jest.fn(),
usePathname: jest.fn(),
useSearchParams: jest.fn(),
}))
describe('NavigationComponent', () => {
it('should navigate to home on button click', () => {
const pushMock = jest.fn()
;(useRouter as jest.Mock).mockReturnValue({
push: pushMock,
back: jest.fn(),
forward: jest.fn(),
})
render(<NavigationComponent />)
const button = screen.getByRole('button', { name: '回到首页' })
fireEvent.click(button)
expect(pushMock).toHaveBeenCalledWith('/')
})
})
usePathname mocken:
import { usePathname } from 'next/navigation'
jest.mock('next/navigation', () => ({
usePathname: jest.fn(),
}))
describe('HeaderComponent', () => {
it('should highlight active nav item', () => {
;(usePathname as jest.Mock).mockReturnValue('/about')
render(<Header />)
const aboutLink = screen.getByRole('link', { name: '关于' })
expect(aboutLink).toHaveClass('active')
})
})
Next.js Image mocken
next/image schlägt in Tests fehl – es braucht den Next.js-Bildoptimierungsdienst.
Option 1: als normales img mocken:
// __mocks__/next/image.tsx
const Image = ({ src, alt }: { src: string; alt: string }) => {
return <img src={src} alt={alt} />
}
export default Image
Option 2: global in jest.config.ts:
const config: Config = {
moduleNameMapper: {
'^next/image$': '<rootDir>/__mocks__/next/image.tsx',
},
}
Alle next/image-Imports werden automatisch ersetzt.
API-Requests mocken (drei Methoden)
Methode 1: global fetch (einfachste):
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ data: 'mock data' }),
})
) as jest.Mock
Methode 2: MSW (Mock Service Worker) (mächtiger):
npm install -D msw
// mocks/handlers.ts
import { http, HttpResponse } from 'msw'
export const handlers = [
http.get('/api/posts', () => {
return HttpResponse.json([
{ id: 1, title: 'Test Post' },
])
}),
http.post('/api/login', async ({ request }) => {
const { email } = await request.json()
if (email === '[email protected]') {
return HttpResponse.json({ success: true })
}
return HttpResponse.json({ error: 'Invalid email' }, { status: 400 })
}),
]
// mocks/server.ts
import { setupServer } from 'msw/node'
import { handlers } from './handlers'
export const server = setupServer(...handlers)
In jest.setup.ts:
import '@testing-library/jest-dom'
import { server } from './mocks/server'
beforeAll(() => server.listen())
afterEach(() => server.resetHandlers())
afterAll(() => server.close())
MSW fängt alle Requests ab – kein global.fetch pro Test.
Methode 3: axios mocken:
npm install -D axios-mock-adapter
import axios from 'axios'
import MockAdapter from 'axios-mock-adapter'
const mock = new MockAdapter(axios)
describe('API Test', () => {
afterEach(() => {
mock.reset()
})
it('should fetch posts', async () => {
mock.onGet('/api/posts').reply(200, [{ id: 1, title: 'Test' }])
const response = await axios.get('/api/posts')
expect(response.data).toHaveLength(1)
})
})
Umgebungsvariablen mocken
Methode 1: process.env direkt setzen:
describe('Config Test', () => {
const originalEnv = process.env
beforeEach(() => {
jest.resetModules()
process.env = { ...originalEnv }
})
afterEach(() => {
process.env = originalEnv
})
it('should use API URL from env', () => {
process.env.NEXT_PUBLIC_API_URL = 'https://test-api.com'
const { getApiUrl } = require('@/lib/config')
expect(getApiUrl()).toBe('https://test-api.com')
})
})
Methode 2: .env.test-Datei:
NEXT_PUBLIC_API_URL=https://test-api.com
DATABASE_URL=postgresql://test:test@localhost:5432/test
next/jest lädt sie automatisch.
Drittanbieter-Module mocken (Beispiel Prisma)
Bei Prisma wollen Sie in Tests keine echte DB.
// __mocks__/prisma.ts
export const prisma = {
user: {
findMany: jest.fn(),
findUnique: jest.fn(),
create: jest.fn(),
update: jest.fn(),
delete: jest.fn(),
},
post: {
findMany: jest.fn(),
create: jest.fn(),
},
}
In Tests:
import { prisma } from '@/lib/prisma'
jest.mock('@/lib/prisma', () => ({
prisma: {
user: {
findUnique: jest.fn(),
},
},
}))
describe('getUserById', () => {
it('should return user', async () => {
const mockUser = { id: 1, name: 'Test User' }
;(prisma.user.findUnique as jest.Mock).mockResolvedValue(mockUser)
const result = await getUserById(1)
expect(result).toEqual(mockUser)
expect(prisma.user.findUnique).toHaveBeenCalledWith({
where: { id: 1 },
})
})
})
Häufige Mock-Fehler
Problem 1: Cannot find module 'next/router'
Ursache: Next.js-Routing nicht gemockt.
Lösung: jest.mock('next/navigation') am Dateianfang.
Problem 2: Mock greift nicht
Ursache: jest.mock falsch platziert – muss oben stehen (nach imports).
import { useRouter } from 'next/navigation'
jest.mock('next/navigation')
describe('Test', () => {
// ...
})
Problem 3: Umgebungsvariablen fehlen
Ursache: .env nicht geladen.
Lösung: jest.config.ts mit next/jest umschließen – lädt Umgebungsvariablen automatisch.
Mit diesen Mock-Techniken lassen sich die meisten Next.js-Besonderheiten testen.
Häufige Probleme und Fehlerbehebung
Beim Jest-Setup sind Fehler normal – 90 % sind wiederkehrend und haben Standardlösungen.
Fehler 1: Cannot use import statement outside a module
Meldung:
SyntaxError: Cannot use import statement outside a module
Ursache: Jest unterstützt ES Modules standardmäßig nicht; Code oder Dependencies nutzen import/export.
Lösung in jest.config.ts:
const config: Config = {
extensionsToTreatAsEsm: ['.ts', '.tsx'],
transformIgnorePatterns: [
'node_modules/(?!(module-that-uses-esm)/)',
],
}
Bei bestimmten Paketen (nanoid, uuid) in die Ausnahmeliste:
transformIgnorePatterns: [
'node_modules/(?!(nanoid|uuid)/)',
]
Fehler 2: Unexpected token ‘export’
Ursache: Datei wird nicht transformiert – ähnlich wie oben.
Lösung: transform und transformIgnorePatterns prüfen; Konfiguration mit next/jest umschließen:
import nextJest from 'next/jest'
const createJestConfig = nextJest({ dir: './' })
export default createJestConfig(config)
next/jest übernimmt TypeScript- und JSX-Transformation.
Fehler 3: Cannot find module ’@/components/…’
Ursache: Pfad-Alias @/ unbekannt.
Lösung: moduleNameMapper in jest.config.ts:
moduleNameMapper: {
'^@/components/(.*)$': '<rootDir>/components/$1',
'^@/lib/(.*)$': '<rootDir>/lib/$1',
'^@/(.*)$': '<rootDir>/$1',
}
Pfade müssen mit tsconfig.json paths übereinstimmen.
Fehler 4: Objects are not valid as a React child
Ursache: async Server Component getestet – Jest unterstützt das nicht.
Lösung:
- async Logik in Pure Functions auslagern
- E2E mit Playwright oder Cypress
Nicht alles mit Jest erzwingen.
Fehler 5: act(…) warning
Meldung:
Warning: An update to Component inside a test was not wrapped in act(...).
Ursache: async State-Update (useEffect, setTimeout) – Test wartet nicht.
Lösung: waitFor oder act:
import { waitFor } from '@testing-library/react'
it('should update state', async () => {
render(<Component />)
await waitFor(() => {
expect(screen.getByText('Updated')).toBeInTheDocument()
})
})
Oder mit act:
import { act } from '@testing-library/react'
it('should trigger callback', async () => {
await act(async () => {
render(<Component />)
})
})
Langsame Tests? Optimierung
- Parallel:
{
"scripts": {
"test": "jest --maxWorkers=4"
}
}
- Nur geänderte Dateien:
npm test -- --onlyChanged
- Coverage deaktivieren (Debug):
{
"scripts": {
"test:fast": "jest --no-coverage"
}
}
test.skipfür langsame Tests:
describe.skip('Slow Tests', () => {
// wird übersprungen
})
Diagnose-Checkliste
Bei Fehlern der Reihe nach:
- ✅
jest.config.tsmitnext/jestumschlossen? - ✅
jest.setup.tsimportiert@testing-library/jest-dom? - ✅ Pfad-Aliase wie in
tsconfig.json? - ✅ Mocks für
next/navigation,next/image? - ✅ async mit
waitForoderact? - ✅ kompatible Dependency-Versionen (besonders React 19 und Jest)?
Die Checkliste löst die meisten Probleme.
Fazit
Die Testumgebung einzurichten ist mühsam – läuft sie erst, hebt sie die Codequalität spürbar an.
Am Anfang wirkte Testen verschwendete Zeit – Feature in fünf Minuten, Tests zehn. Später: beim Refactoring und Bugfixing deutlich ruhiger. Tests grün – gut; rot – Sie wissen, was kaputtging.
Empfehlungen:
- Nicht warten, bis das Projekt riesig ist – ab jetzt Tests zu neuen Features.
- Keine 100-%-Coverage erzwingen – Kernlogik und fehleranfällige Stellen reichen.
- Server Components nicht mit Jest erzwingen – Logik auslagern oder E2E.
- Bei Fehlern die Checkliste durchgehen – meist schnell gelöst.
Konfigurationsdateien aus diesem Artikel speichern – beim nächsten Projekt kopieren, in zehn Minuten läuft die Testumgebung. Weniger Bugs, bessere Qualität.
Fragen bei Konfiguration oder Tests? Gerne in den Kommentaren – ich bin durch dieselben Fallen gelaufen und helfe, wo ich kann.
Next.js Jest Testumgebung komplett einrichten
Schritt-für-Schritt-Anleitung zur Einrichtung von Next.js 15 + Jest + React Testing Library
⏱️ Estimated time: 15 min
- 1
Step 1: Test-Abhängigkeiten installieren
Jest und React Testing Library installieren:
• npm install -D jest jest-environment-jsdom
• npm install -D @testing-library/react @testing-library/dom @testing-library/jest-dom
• npm install -D ts-node @types/jest (für TypeScript-Projekte)
Paketübersicht:
• jest: Test-Framework-Kern
• jest-environment-jsdom: simuliert Browser-DOM-Umgebung
• @testing-library/react: React-Komponenten-Tests
• @testing-library/jest-dom: erweiterte Assertions (z. B. toBeInTheDocument)
Nach der Installation Tests noch nicht ausführen – zuerst Konfigurationsdateien anlegen. - 2
Step 2: Jest-Konfigurationsdatei erstellen
jest.config.ts im Projektroot anlegen und next/jest für Next.js-Features nutzen:
```typescript
import type { Config } from 'jest'
import nextJest from 'next/jest'
const createJestConfig = nextJest({ dir: './' })
const config: Config = {
coverageProvider: 'v8',
testEnvironment: 'jsdom',
setupFilesAfterEnv: ['<rootDir>/jest.setup.ts'],
// Bei Pfad-Alias moduleNameMapper ergänzen
moduleNameMapper: {
'^@/components/(.*)$': '<rootDir>/components/$1',
'^@/lib/(.*)$': '<rootDir>/lib/$1',
},
}
export default createJestConfig(config)
```
next/jest übernimmt automatisch: CSS/Bild-Mocks, Umgebungsvariablen, TypeScript-Transformation, node_modules-Ausschluss. - 3
Step 3: Jest-Setup-Datei erstellen
jest.setup.ts im Projektroot anlegen und Jest-DOM-Erweiterungen importieren:
```typescript
import '@testing-library/jest-dom'
```
Damit stehen erweiterte Assertions zur Verfügung:
• expect(element).toBeInTheDocument()
• expect(element).toHaveClass('active')
• expect(element).toBeVisible()
• expect(element).toHaveTextContent('text')
Ohne diesen Import führen die Methoden zu „not a function“-Fehlern. - 4
Step 4: Test-Skripte konfigurieren
Test-Befehle in package.json ergänzen:
```json
{
"scripts": {
"test": "jest",
"test:watch": "jest --watch",
"test:coverage": "jest --coverage"
}
}
```
Drei Befehle:
• npm test: alle Tests (CI/CD)
• npm run test:watch: Watch-Modus bei Dateiänderungen (Entwicklung)
• npm run test:coverage: Code-Coverage-Bericht - 5
Step 5: Beispieltest zur Konfigurationsprüfung
__tests__/example.test.ts anlegen und Umgebung prüfen:
```typescript
describe('Example Test', () => {
it('should pass basic assertion', () => {
expect(1 + 1).toBe(2)
})
})
```
npm test ausführen – grünes PASS bedeutet Erfolg.
Bei Fehlern der Reihe nach prüfen:
1. jest.config.ts mit createJestConfig umschlossen?
2. jest.setup.ts korrekt importiert?
3. package.json-Skripte korrekt?
4. Pfad-Alias wie in tsconfig.json?
FAQ
Warum muss die Konfiguration mit next/jest umschlossen werden?
• automatisches Mocken von CSS und Bildern
• automatisches Laden von .env-Umgebungsvariablen
• automatische Transformation von TypeScript und JSX
• automatischer Ausschluss von node_modules und .next
• Next.js-Compiler-Transformationsregeln
Ohne next/jest müssten Sie all das manuell konfigurieren – fehleranfällig und aufwendig. Offiziell wird createJestConfig empfohlen.
Kann man Server Components mit Jest testen?
• synchrone Server Components: normal testbar
• async Server Components: Jest unterstützt sie nicht – Fehler „Objects are not valid as a React child“
Empfohlen:
1. async Logik in Pure Functions auslagern und Datenabruf separat testen
2. Server Components nur einfach rendern, ohne komplexe Logik
3. Playwright oder Cypress für E2E über den gesamten Flow
Nicht alles mit Jest erzwingen – das richtige Werkzeug wählen.
Wie mocke ich useRouter in Tests?
```typescript
import { useRouter } from 'next/navigation'
jest.mock('next/navigation', () => ({
useRouter: jest.fn(),
usePathname: jest.fn(),
useSearchParams: jest.fn(),
}))
const pushMock = jest.fn()
;(useRouter as jest.Mock).mockReturnValue({
push: pushMock,
back: jest.fn(),
forward: jest.fn(),
})
```
jest.mock muss oben in der Testdatei stehen (nach imports), nicht in describe oder it.
Warum tritt der Fehler ‚Cannot use import statement outside a module‘ auf?
Option 1: transformIgnorePatterns (empfohlen)
```typescript
const config: Config = {
extensionsToTreatAsEsm: ['.ts', '.tsx'],
transformIgnorePatterns: [
'node_modules/(?!(nanoid|uuid)/)',
],
}
```
Option 2: next/jest korrekt umschließen
```typescript
import nextJest from 'next/jest'
const createJestConfig = nextJest({ dir: './' })
export default createJestConfig(config)
```
In 90 % der Fälle nutzt ein npm-Paket ESM – Paketname in transformIgnorePatterns-Ausnahmeliste.
Wie mocke ich API-Requests? Welche Methode ist empfohlen?
Methode 1: global fetch mocken (einfach, kleine Projekte)
```typescript
global.fetch = jest.fn(() =>
Promise.resolve({
ok: true,
json: () => Promise.resolve({ data: 'test' }),
})
) as jest.Mock
```
Methode 2: MSW (Mock Service Worker, empfohlen für mittlere/große Projekte)
• alle Netzwerk-Requests abfangen
• komplexe Request/Response-Logik
• kein Mock pro Test nötig
• Installation: npm install -D msw
Methode 3: axios-mock-adapter (bei axios)
• speziell für axios
• einfache API
MSW empfiehlt sich wegen realistischerer Requests und Wiederverwendbarkeit im Team.
Tests laufen langsam – wie optimieren?
1. Parallel (am effektivsten)
```json
{ "scripts": { "test": "jest --maxWorkers=4" } }
```
2. Nur geänderte Dateien
```bash
npm test -- --onlyChanged
```
3. Coverage deaktivieren (beim Debuggen)
```json
{ "scripts": { "test:fast": "jest --no-coverage" } }
```
4. langsame Tests überspringen
```typescript
describe.skip('Slow E2E Tests', () => {
// wird übersprungen
})
```
Entwicklung: --onlyChanged und --no-coverage; CI/CD: vollständiger Lauf.
Pfad-Alias @/ konfiguriert, Tests schlagen trotzdem fehl?
tsconfig.json (oder jsconfig.json):
```json
{
"compilerOptions": {
"baseUrl": "./",
"paths": {
"@/components/*": ["components/*"],
"@/lib/*": ["lib/*"]
}
}
}
```
jest.config.ts:
```typescript
moduleNameMapper: {
'^@/components/(.*)$': '<rootDir>/components/$1',
'^@/lib/(.*)$': '<rootDir>/lib/$1',
}
```
Wichtig:
1. tsconfig: relative Pfade (ohne <rootDir>)
2. jest.config: absolute Pfade (mit <rootDir>)
3. Wildcard-Syntax konsistent (.*)
Nach Änderung Tests neu starten.
13 Min. Lesezeit · Veröffentlicht am: 7. Jan. 2026 · Aktualisiert am: 14. Juli 2026
Next.js Komplettleitfaden
Wenn du über die Suche hier gelandet bist, kommst du am schnellsten weiter, indem du zum vorherigen oder nächsten Beitrag dieser Serie springst.
Vorheriger
Next.js Error Boundary: Vollständiger Leitfaden – 5 Tipps für elegante Laufzeitfehlerbehandlung
Next.js Error Boundary im Detail: error.tsx, globales Error Handling, Besonderheiten bei Server Components und Wiederherstellungsmechanismen – so vermeiden Sie weiße Bildschirme und verbessern die UX.
Teil 34 von 51
Nächster
Next.js E2E-Tests: Praxisleitfaden für automatisierte Tests mit Playwright
Vom manuellen Testen zu automatisierten E2E-Tests: Playwright-Konfiguration, Page Object Model, API-Tests und CI/CD-Integration – mit konkreten Praxisbeispielen.
Teil 36 von 51
Ähnliche Beiträge
Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick

Next.js App Router Einstieg: Kernkonzepte und Grundlagen im Überblick
Next.js 15 in der Praxis: Wie ich an einem Wochenende ein produktionsreifes Blog-System aufgebaut habe


Kommentare
Melde dich mit GitHub an, um einen Kommentar zu hinterlassen