Vitest mit Claude Code: Unit Testing für Vite-Projekte 2026

Vitest ist der schnellste Unit-Test-Framework für Vite-Projekte — native ESM, identische Konfiguration mit vite.config.ts, integriertes Mocking. Claude Code generiert vollständige Test-Suites: Unit-Tests, Component-Tests mit Testing Library und Coverage-Konfiguration.

Setup und Konfiguration

SetupVitest in Vite-Projekt einrichten

# Prompt: "Vitest Setup für React TypeScript Projekt mit jsdom und Testing Library" npm install -D vitest @vitest/ui @vitest/coverage-v8 npm install -D @testing-library/react @testing-library/user-event @testing-library/jest-dom // vite.config.ts — Vitest-Konfiguration eingebettet import { defineConfig } from 'vitest/config' import react from '@vitejs/plugin-react' export default defineConfig({ plugins: [react()], test: { environment: 'jsdom', // Browser-ähnlich globals: true, // describe/it/expect ohne import setupFiles: ['./src/test/setup.ts'], coverage: { provider: 'v8', reporter: ['text', 'html'], thresholds: { lines: 80, functions: 80 } } } }) // src/test/setup.ts import '@testing-library/jest-dom' // toBeInTheDocument() etc. // package.json scripts "test": "vitest", "test:ui": "vitest --ui", "coverage": "vitest run --coverage"

Unit Tests: Pure Functions

UnitService-Funktionen testen

# Prompt: "Unit Tests für UserService: create, findById, update — mit Prisma-Mock" // src/services/userService.test.ts import { describe, it, expect, beforeEach } from 'vitest' import { UserService } from './userService' import { formatCurrency, slugify, clamp } from '../utils' describe('formatCurrency', () => { it('formatiert Euro korrekt', () => { expect(formatCurrency(1234.56, 'EUR')).toBe('1.234,56 €') }) it('behandelt Null-Werte', () => { expect(formatCurrency(0, 'EUR')).toBe('0,00 €') }) }) describe('slugify', () => { it.each([ ['Mein Artikel', 'mein-artikel'], ['Über Claude!', 'uber-claude'], ['Hello World', 'hello-world'] ])('%s → %s', (input, expected) => { expect(slugify(input)).toBe(expected) }) }) describe('clamp', () => { it('begrenzt auf Min/Max', () => { expect(clamp(-5, 0, 100)).toBe(0) expect(clamp(150, 0, 100)).toBe(100) expect(clamp(50, 0, 100)).toBe(50) }) })

Mocking: Abhängigkeiten ersetzen

MockingModule, API-Calls und Timer mocken

# Prompt: "Mocking für Prisma-Client, fetch-Calls und Date.now() in Vitest" import { vi, describe, it, expect, beforeEach } from 'vitest' // Modul komplett mocken vi.mock('@/lib/db', () => ({ db: { user: { findUnique: vi.fn(), create: vi.fn(), update: vi.fn() } } })) import { db } from '@/lib/db' import { UserService } from './userService' describe('UserService', () => { beforeEach(() => vi.clearAllMocks()) it('erstellt User und gibt ihn zurück', async () => { const mockUser = { id: '1', name: 'Max', email: 'max@test.com' } vi.mocked(db.user.create).mockResolvedValue(mockUser) const result = await UserService.create({ name: 'Max', email: 'max@test.com' }) expect(result).toEqual(mockUser) expect(db.user.create).toHaveBeenCalledWith({ data: { name: 'Max', email: 'max@test.com' } }) }) it('wirft NotFoundError wenn User nicht existiert', async () => { vi.mocked(db.user.findUnique).mockResolvedValue(null) await expect(UserService.findById('999')).rejects.toThrow('NotFoundError') }) }) // Timer-Mocking für Debounce/Throttle it('debounce verzögert Ausführung', () => { vi.useFakeTimers() const fn = vi.fn() const debouncedFn = debounce(fn, 300) debouncedFn() debouncedFn() expect(fn).not.toHaveBeenCalled() vi.advanceTimersByTime(300) expect(fn).toHaveBeenCalledTimes(1) vi.useRealTimers() })

Component Tests mit Testing Library

ReactKomponenten testen mit @testing-library/react

# Prompt: "React Component Tests für LoginForm: Render, User-Interaktion, Submission" import { render, screen } from '@testing-library/react' import userEvent from '@testing-library/user-event' import { LoginForm } from './LoginForm' describe('LoginForm', () => { const user = userEvent.setup() it('rendert Email und Passwort Felder', () => { render(<LoginForm onSubmit={vi.fn()} />) expect(screen.getByLabelText('E-Mail')).toBeInTheDocument() expect(screen.getByLabelText('Passwort')).toBeInTheDocument() }) it('ruft onSubmit mit Formulardaten auf', async () => { const onSubmit = vi.fn() render(<LoginForm onSubmit={onSubmit} />) await user.type(screen.getByLabelText('E-Mail'), 'max@test.com') await user.type(screen.getByLabelText('Passwort'), 'password123') await user.click(screen.getByRole('button', { name: 'Anmelden' })) expect(onSubmit).toHaveBeenCalledWith({ email: 'max@test.com', password: 'password123' }) }) it('zeigt Validierungsfehler bei leerem Formular', async () => { render(<LoginForm onSubmit={vi.fn()} />) await user.click(screen.getByRole('button', { name: 'Anmelden' })) expect(screen.getByText('E-Mail ist erforderlich')).toBeInTheDocument() }) })
Testing Library Prinzip: Tests sollten das Verhalten aus User-Perspektive prüfen — getByRole, getByLabelText, getByText statt CSS-Selektoren oder Test-IDs. Claude Code folgt diesem Prinzip automatisch.

Coverage und CI

# Coverage Report erzeugen npx vitest run --coverage # → coverage/ Ordner mit HTML-Report # → Zeigt welche Zeilen/Branches nicht getestet sind # Snapshot Testing für UI-Komponenten it('Button rendert korrekt', () => { const { container } = render(<Button variant="primary">Click me</Button>) expect(container).toMatchSnapshot() // → Snapshot in __snapshots__/ gespeichert // → Update: npx vitest run --update-snapshots }) # Vitest Watch Mode für TDD npx vitest --watch # → Re-runs bei Dateiänderung, zeigt sofort Ergebnis # Claude Code Vitest-Prompts: # "Schreibe Unit Tests für alle exports aus utils.ts — 100% Branch Coverage" # "Füge Test für den Error-Case in fetchUser() hinzu" # "Refaktoriere diese Tests: DRY-Principle, beforeEach für gemeinsamen Setup"

Testing-Modul im Kurs

Im Claude Code Mastery Kurs: vollständiges Testing-Modul mit Vitest, React Testing Library, Mocking-Strategien, Coverage-Setup und TDD-Workflow — für wartbare, gut getestete React-Applikationen.

14 Tage kostenlos testen →