Claude Code & Vitest: Blitzschnelle Unit-Tests für JavaScript
Unit-Tests sind das Fundament jedes seriösen JavaScript-Projekts — aber ehrlich gesagt sind sie auch das, was viele Entwickler am liebsten auf später verschieben. Setup, Mocking-Boilerplate, Snapshot-Pflege: der Overhead fühlt sich häufig größer an als der Nutzen. Vitest löst das Setup-Problem, Claude Code löst das Schreib-Problem. Zusammen sinkt die Einstiegsbarriere so weit, dass Tests aufzuschieben keinen guten Grund mehr hat.
Dieser Artikel zeigt, wie du Vitest in einem Vite-Projekt aufsetzt, was vi.mock, vi.fn und vi.spyOn leisten, wie Snapshot-Tests und Coverage funktionieren — und wie Claude Code dir dabei den größten Teil der Arbeit abnimmt.
Claude Code Mastery — Testing, Agents, Hooks auf Deutsch
Nicht nur Vitest: der Kurs zeigt, wie du Claude Code für vollständige Entwicklungsworkflows einsetzt — von der Test-Suite bis zum autonomen Agenten. Einmalig bezahlt, kein Abo.
Zum Kurs — Jetzt starten → Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht1. Vitest vs. Jest: Warum der Wechsel sich lohnt
Jest war jahrelang der Standard für JavaScript-Unit-Tests — und ist es in vielen Legacy-Projekten immer noch. Aber wer heute ein neues Projekt mit Vite aufbaut, hat einen starken Grund, direkt zu Vitest zu greifen: kein separater Transform-Layer. Vitest verwendet dieselbe Vite-Pipeline wie dein Entwicklungsserver. Das bedeutet, dass deine vite.config.ts auch für Tests gilt — Aliases, Plugins, Environment-Variablen funktionieren ohne Doppelkonfiguration.
Der zweite Unterschied ist die Geschwindigkeit. Vitest nutzt native ES-Module und führt Tests parallel in Worker-Threads aus. In der Praxis bedeutet das: eine Test-Suite, die mit Jest 8–12 Sekunden braucht, läuft mit Vitest in 1–3 Sekunden. Im Watch-Modus fühlt sich das wie ein anderes Werkzeug an.
- Kein Babel-Setup — TypeScript und JSX funktionieren out-of-the-box
- Kompatible API —
describe,it,expectsind identisch zu Jest - HMR-Integration — geänderte Dateien werden sofort neu getestet
- Native ESM — kein CommonJS-Shimming, keine Interop-Probleme
Migration von Jest: Die API-Kompatibilität ist hoch — in den meisten Projekten reicht es, jest durch vitest zu ersetzen und die Imports anzupassen. Claude Code erledigt diese Migration zuverlässig: einfach eine bestehende Jest-Testdatei übergeben und um Konvertierung bitten.
2. Setup: vitest.config.ts und erste Tests
Installation in einem Vite-Projekt:
npm install -D vitest @vitest/coverage-v8
Die Konfiguration gehört entweder direkt in vite.config.ts oder in eine separate vitest.config.ts:
// vitest.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
environment: 'jsdom', // oder 'node' für reine Backend-Logik
globals: true, // describe/it/expect ohne Import
setupFiles: ['./src/test/setup.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
exclude: ['node_modules', 'src/test']
}
}
})
Claude Code kann diese Konfiguration für dein Projekt erzeugen — inklusive der richtigen environment-Einstellung, abhängig davon, ob du DOM-Tests oder reine Node-Logik testest:
claude "Erstelle eine vitest.config.ts für ein React-Projekt mit jsdom,
globals, Coverage mit v8 und einem Setup-File für Testing Library"
Erster Test mit describe, it und expect
Die Grundstruktur ist identisch zu Jest:
// src/utils/format.test.ts
import { describe, it, expect } from 'vitest'
import { formatCurrency } from './format'
describe('formatCurrency', () => {
it('formatiert positive Beträge korrekt', () => {
expect(formatCurrency(1234.5, 'EUR')).toBe('1.234,50 €')
})
it('behandelt null als Null-Betrag', () => {
expect(formatCurrency(null, 'EUR')).toBe('0,00 €')
})
it('wirft bei unbekannter Währung einen Fehler', () => {
expect(() => formatCurrency(100, 'XYZ')).toThrow('Unbekannte Währung')
})
})
3. Mocking: vi.mock, vi.fn und vi.spyOn
Mocking ist der Teil, bei dem Jest-Neulinge am längsten hängen. Vitest löst das mit einer kompakten, gut dokumentierten API, die Claude Code ausgezeichnet beherrscht.
vi.mock — Module ersetzen
vi.mock ersetzt ein komplettes Modul durch eine Mock-Implementierung. Typischer Anwendungsfall: API-Calls in Tests unterbinden.
import { vi, describe, it, expect, beforeEach } from 'vitest'
import { getUser } from './userService'
import * as api from '../lib/api'
vi.mock('../lib/api')
describe('getUser', () => {
beforeEach(() => {
vi.mocked(api.fetchUser).mockResolvedValue({
id: 1,
name: 'Anna Muster',
email: 'anna@example.com'
})
})
it('gibt den User mit korrektem Namen zurück', async () => {
const user = await getUser(1)
expect(user.name).toBe('Anna Muster')
expect(api.fetchUser).toHaveBeenCalledWith(1)
})
})
vi.fn — Einzelne Funktionen mocken
vi.fn erzeugt eine Mock-Funktion, die Aufrufe aufzeichnet und beliebige Rückgabewerte liefern kann:
const onSubmit = vi.fn()
onSubmit.mockReturnValue(true)
onSubmit('test-data')
expect(onSubmit).toHaveBeenCalledOnce()
expect(onSubmit).toHaveBeenCalledWith('test-data')
vi.spyOn — Implementierungen überwachen
vi.spyOn ist nützlich, wenn du eine Methode überwachen willst, ohne sie vollständig zu ersetzen:
import { vi } from 'vitest'
import * as logger from './logger'
const spy = vi.spyOn(logger, 'warn')
// ... Code ausführen der warn aufruft
expect(spy).toHaveBeenCalledWith('Ungültiger Wert')
Claude Code schreibt Mocks zuverlässig und wählt automatisch die richtige Variante — vi.mock für Modul-Grenzen, vi.fn für Callbacks, vi.spyOn für partielle Überwachung. Einfach die zu testende Funktion zeigen und fragen:
claude "Schreib einen Vitest für diese Funktion, mock den API-Call
und prüfe dass die Fehlerbehandlung greift wenn der Server 500 zurückgibt"
4. Snapshot-Tests
Snapshot-Tests sichern die Ausgabe einer Komponente oder Funktion ab — beim ersten Lauf wird ein Snapshot gespeichert, bei jedem weiteren Lauf wird verglichen. Abweichungen schlagen fehl, beabsichtigte Änderungen werden mit vitest --update-snapshots akzeptiert.
import { describe, it, expect } from 'vitest'
import { render } from '@testing-library/react'
import { ProductCard } from './ProductCard'
describe('ProductCard', () => {
it('rendert korrekt (Snapshot)', () => {
const { container } = render(
<ProductCard
name="Kopfhörer Pro"
price={149.99}
inStock={true}
/>
)
expect(container).toMatchSnapshot()
})
})
Snapshot-Disziplin: Snapshots sind nützlich für stabile UI-Komponenten, aber sie verfallen schnell zur Pflicht ohne Erkenntnisgewinn, wenn man sie blind akzeptiert. Lass Claude Code beim Review helfen: übergib den Snapshot-Diff und frag, ob die Änderung beabsichtigt oder ein Regressionszeichen ist.
5. Coverage mit @vitest/coverage-v8
Coverage-Berichte zeigen, welche Code-Pfade deine Tests abdecken. Mit dem bereits konfigurierten @vitest/coverage-v8 genügt:
npx vitest run --coverage
Das erzeugt einen Text-Report im Terminal und eine HTML-Ansicht unter coverage/index.html. Typische Ziele im Produktivbetrieb: 80 % Statement-Coverage für Kern-Logik, 60 % insgesamt. Coverage-Schwellen in der Konfiguration durchsetzen:
coverage: {
thresholds: {
statements: 80,
branches: 70,
functions: 75,
lines: 80
}
}
Claude Code analysiert deinen Coverage-Report und identifiziert, welche Pfade noch nicht abgedeckt sind — und schreibt direkt die fehlenden Tests:
claude "Hier ist mein Coverage-Report. Schreib Tests für die
uncovered branches in src/auth/session.ts" < coverage/coverage-summary.json
6. UI-Tests mit jsdom
Für React-, Vue- oder Svelte-Komponenten braucht Vitest eine DOM-Emulation. Mit environment: 'jsdom' in der Konfiguration und @testing-library/react funktionieren vollständige Render-Tests ohne Browser:
import { render, screen, fireEvent } from '@testing-library/react'
import { userEvent } from '@testing-library/user-event'
import { LoginForm } from './LoginForm'
it('blockiert Submit bei leerem Passwort', async () => {
const onLogin = vi.fn()
render(<LoginForm onLogin={onLogin} />)
await userEvent.type(screen.getByLabelText('E-Mail'), 'test@example.com')
await userEvent.click(screen.getByRole('button', { name: /anmelden/i }))
expect(screen.getByText('Passwort ist erforderlich')).toBeInTheDocument()
expect(onLogin).not.toHaveBeenCalled()
})
Claude Code schreibt diese Tests besonders effektiv, weil es die Komponente liest, ihre Props und ihren internen Zustand versteht und daraus sinnvolle Testszenarien ableitet — statt generischer Assertions, die zwar grün werden, aber nichts Relevantes prüfen.
7. Claude Code-Tipps für Vitest
Einige Prompts, die im Alltag besonders gut funktionieren:
- Test-Suite für eine neue Datei:
claude "Schreib vollständige Vitest-Tests für src/utils/cart.ts, inkl. Edge Cases und Fehlerbehandlung" - Mock-Generierung:
claude "Mock den Stripe-Client für diesen Test und simuliere einen fehlgeschlagenen Payment Intent" - Test-Refactoring:
claude "Diese Tests haben viel Duplikation. Refactore sie mit beforeEach und gemeinsamen Fixtures" - Coverage-Lücken schließen:
claude "Welche Branches in dieser Funktion sind noch nicht getestet? Schreib die fehlenden Tests" - Jest-Migration:
claude "Konvertiere diese Jest-Testdatei zu Vitest, behalte alle Assertions bei"
"Das Schreiben von Tests ist die Arbeit, die am meisten profitiert, wenn man Claude Code Zugriff auf den vollständigen Code-Kontext gibt. Die Qualität der Assertions steigt dramatisch, sobald Claude Code die Implementierung kennt und nicht nur die Funktion erraten muss."
Zwei verwandte Artikel aus dieser Serie:
- Claude Code Debugging — Stack Traces analysieren, Root-Cause finden, Bugs in Minuten beheben
- Claude Code für Python-Projekte — pytest, Typen und Projektstruktur mit Claude Code
Claude Code Mastery — von Unit-Tests bis zum produktiven Agenten
Vitest ist ein Kapitel. Im Kurs lernst du Agents, MCP-Server, Hooks, Multi-Agent-Workflows und mehr. Vollständig auf Deutsch, einmalig bezahlt.
Jetzt starten → Einmalzahlung · Kein Abo · 14 Tage RückgaberechtKurs · Claude Code Mastery
Von Unit-Tests zum produktiven AI-Agenten
Testing. Agents. MCP. Hooks. Multi-Agent-Workflows. Alles auf Deutsch, einmalig bezahlt — kein Abo, keine Plattformabhängigkeit.
Einmalzahlung · Kein Abo · 14 Tage Rückgaberecht