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 →