Vitest Unit-Tests in der Praxis: Von der Konfiguration zum TDD-Workflow

Wie lange brauchen Sie, um Jest in einem ESM-Projekt zum Laufen zu bringen? ts-jest, babel-jest, jest.config.js … und dann noch all die Modulauflösungs-Probleme. Ich habe schon einen ganzen Nachmittag damit verbracht, damit Jest .vue-Importe korrekt erkennt.
Bei Vitest reicht oft eine Zeile Konfiguration.
Das ist keine Übertreibung. Als ich in einem Vite-Projekt zum ersten Mal vitest startete und die Tests in wenigen Sekunden durchliefen, fühlte es sich an wie der Wechsel von einem drei Jahre alten, trägen Rechner – erfrischend.
Wie schnell ist Vitest? Offizielle Daten und Community-Messungen zeigen: Cold Start etwa 200 ms (Jest: 2–4 Sekunden), 500 Testfälle in rund 8 Sekunden (Jest: etwa 45 Sekunden). Dazu teilt es die Vite-Konfiguration, unterstützt TypeScript nativ und die API gleicht Jest fast vollständig – die Migration dauert oft nur eine halbe Stunde.
Dieser Artikel führt Sie von der Grundkonfiguration bis zum vollständigen TDD-Workflow – inklusive Mocking und Coverage. Ob neues Vitest-Projekt oder Migration von Jest: Hier finden Sie, was Sie brauchen.
Was ist Vitest – und warum ist es so schnell?
Kurz gesagt: Vitest ist das native Testframework von Vite.
Wenn Sie bereits mit Vite bauen, ist Vitest praktisch sofort einsatzbereit. Es übernimmt die Vite-Konfiguration – Aliase, Umgebungsvariablen, CSS-Verarbeitung. Kein extra Jest-Setup mit transform, moduleFileExtensions, moduleNameMapper.
Die drei Kernvorteile:
Geschwindigkeit. Cold Start etwa 200 Millisekunden, Jest braucht typischerweise 2–4 Sekunden. In großen Projekten wird der Unterschied deutlich: 500 Tests in ~8 Sekunden bei Vitest, ~45 Sekunden bei Jest (Messungen der DEV Community, 2026). Kein marginaler Vorsprung.
Jest-Kompatibilität. Die API ist nahezu identisch – describe, it, expect, vi.fn(), Syntax wie bei Jest. Migration heißt meist: Import-Pfade anpassen.
Intelligentes Watching. „HMR for tests“: Nach Codeänderungen laufen nur betroffene Tests neu, nicht die gesamte Suite. Sofortiges Feedback beim Entwickeln.
Genug zur Theorie – als Nächstes die Installation.
Installation und Konfiguration
Systemvoraussetzungen: Vite ≥ 6.0.0, Node ≥ 20.0.0. In aktuellen Projekten sind diese Versionen meist schon erfüllt.
Installation
Ein Befehl:
npm install -D vitest
Das war’s. Kein @types/jest, ts-jest, jest-environment-jsdom … Vitest unterstützt TypeScript nativ.
Konfigurationsdatei
Zwei Wege: test-Feld in vite.config.ts oder separate vitest.config.ts.
Für einfache Projekte reicht vite.config.ts:
// vite.config.ts
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true, // Globale Variablen, kein Import von describe, it, expect nötig
environment: 'node', // oder 'jsdom' (Browser-Umgebung)
include: ['tests/**/*.test.ts'],
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'],
},
},
})
globals: true ist praktisch – danach sind describe, it, expect global verfügbar, wie bei Jest. Kein Import pro Testdatei.
Für DOM-APIs (z. B. Komponenten-Rendering) environment auf 'jsdom' setzen und jsdom installieren:
npm install -D jsdom
Run-Skripte
In package.json zwei Zeilen ergänzen:
{
"scripts": {
"test": "vitest",
"test:run": "vitest run"
}
}
npm test startet den Watch-Modus (Tests laufen bei Änderungen neu), npm run test:run führt einmal aus und beendet (für CI).
So schlank im Vergleich zu Jest mit preset, transform, moduleFileExtensions.
Unit-Tests schreiben
Testdateien heißen üblicherweise .test.ts oder .spec.ts, im Ordner tests/ oder neben der Quelldatei – je nach Teamkonvention.
Grundstruktur
Ein minimaler Test:
import { describe, it, expect } from 'vitest'
import { add, divide } from './math'
describe('Math utilities', () => {
it('should add two numbers', () => {
expect(add(2, 3)).toBe(5)
})
it('should throw on division by zero', () => {
expect(() => divide(10, 0)).toThrow('Division by zero')
})
})
describe gruppiert Tests, it definiert einen Fall, expect prüft das Ergebnis.
Mit globals: true entfällt die Import-Zeile.
Häufige Assertions
Die wichtigsten:
// Gleichheit
expect(value).toBe(5) // Strikte Gleichheit (===)
expect(obj).toEqual({ a: 1 }) // Tiefe Gleichheit
// Wahrheitswerte
expect(value).toBeTruthy()
expect(value).toBeFalsy()
expect(value).toBeNull()
// Exceptions
expect(() => fn()).toThrow()
expect(() => fn()).toThrow('Error message')
// Zahlenvergleich
expect(n).toBeGreaterThan(10)
expect(n).toBeLessThanOrEqual(5)
// Array/String
expect(arr).toContain('item')
expect(str).toMatch(/pattern/)
Tests filtern
Nur einen Test ausführen? .only:
it.only('nur dieser Test', () => { ... })
Einen Test überspringen? .skip:
it.skip('vorerst auslassen', () => { ... })
Gilt auch für describe: describe.only(...), describe.skip(...).
Im Watch-Modus sehen Sie nach jeder Änderung sofort grün oder rot – deutlich angenehmer als bei Jest, wo der Start jedes Mal mehrere Sekunden kostet.
TDD-Workflow in der Praxis
TDD (Test-Driven Development) bedeutet: zuerst Tests, dann Code.
Das klingt kontraintuitiv, zwingt Sie aber dazu, vor dem Codieren zu klären, was eine Funktion leisten soll – statt nachträglich Tests „für Coverage“ anzuhängen.
Im Folgenden ein vollständiger Durchlauf: eine E-Mail-Validierungsfunktion validateEmail.
Schritt 1: Test schreiben, noch ohne Implementierung
Zuerst die Testdatei:
// tests/validateEmail.test.ts
import { describe, it, expect } from 'vitest'
import { validateEmail } from '../src/validateEmail'
describe('validateEmail', () => {
it('should return true for valid email', () => {
expect(validateEmail('[email protected]')).toBe(true)
})
it('should return false for invalid email', () => {
expect(validateEmail('invalid')).toBe(false)
})
})
validateEmail existiert noch nicht – der Test schlägt fehl. Genau das ist TDD-Schritt eins.
Schritt 2: Minimalen Code implementieren
Funktion anlegen, minimal implementieren:
// src/validateEmail.ts
export function validateEmail(email: string): boolean {
return /^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)
}
Tests starten: npm test.
Beide Tests grün? Schritt 2 erledigt.
Schritt 3: Grenzfälle ergänzen
Grundfälle bestehen – E-Mail-Validierung hat mehr Randfälle:
// tests/validateEmail.test.ts (ergänzen)
it('should return false for empty string', () => {
expect(validateEmail('')).toBe(false)
})
it('should return false for email without domain', () => {
expect(validateEmail('user@')).toBe(false)
})
it('should return false for email with spaces', () => {
expect(validateEmail('test @example.com')).toBe(false)
})
Tests laufen – schlagen welche fehl, Regex anpassen.
Schritt 4: Refactoring
Alle Tests grün – jetzt sicher refaktorisieren, z. B. strengeres Regex oder Kommentare:
// src/validateEmail.ts
const EMAIL_REGEX = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
export function validateEmail(email: string): boolean {
if (!email || email.trim() === '') {
return false
}
return EMAIL_REGEX.test(email)
}
Erneut testen – weiterhin grün. Das ist der TDD-Vorteil: Refactoring mit Testnetz.
Warum TDD sich lohnt
Am Anfang wirkte „Tests zuerst“ ungewohnt. Nach ein paar Durchläufen wurde klar:
- Erst denken, dann coden: Beim Testschreiben definieren Sie Verhalten und Ein-/Ausgaben.
- Schnelle Iteration: Vitest Watch-Modus liefert Feedback in Sekunden.
- Sicheres Refactoring: Tests fangen Regressionen sofort ab.
Starten Sie mit einfachen Hilfsfunktionen – Formatierung, Validierung. Danach komplexere Logik.
Mocking – fortgeschrittene Techniken
Unit-Tests isolieren oft externe Abhängigkeiten – API-Aufrufe, Datenbank, Drittanbieter. Dafür brauchen Sie Mocks.
Vitest bietet eine Jest-ähnliche Mock-API um das vi-Objekt.
vi.fn(): Einzelne Funktion mocken
Einfachste Variante – Fake-Funktion:
import { vi, describe, it, expect } from 'vitest'
describe('vi.fn() demo', () => {
it('tracks calls', () => {
const mockFn = vi.fn()
mockFn('hello')
mockFn('world')
expect(mockFn).toHaveBeenCalledTimes(2)
expect(mockFn).toHaveBeenNthCalledWith(1, 'hello')
})
})
Rückgabewerte vorgeben:
const mockFn = vi.fn().mockReturnValue('mocked result')
// oder asynchron
const asyncMock = vi.fn().mockResolvedValue({ data: 'ok' })
vi.mock(): Ganzes Modul mocken
Funktion hängt von externer API ab? Modul mocken:
import { vi, describe, it, expect, beforeEach } from 'vitest'
import { fetchUser } from './api'
import { UserService } from './UserService'
// api-Modul mocken
vi.mock('./api', () => ({
fetchUser: vi.fn().mockResolvedValue({ id: 1, name: 'Alice' })
}))
describe('UserService', () => {
beforeEach(() => {
vi.clearAllMocks() // Aufrufprotokoll vor jedem Test leeren
})
it('should fetch user', async () => {
const service = new UserService()
const user = await service.getUser(1)
expect(user.name).toBe('Alice')
expect(fetchUser).toHaveBeenCalledWith(1)
})
})
vi.mock() läuft vor dem Modulimport – deshalb oben in der Datei platzieren.
vi.spyOn(): Echte Funktion überwachen
Manchmal soll die Funktion weiterlaufen, nur Aufrufe mitprotokolliert werden:
import { vi, describe, it, expect, afterEach } from 'vitest'
import { calculator } from './calculator'
describe('spyOn demo', () => {
afterEach(() => {
vi.restoreAllMocks()
})
it('tracks add calls', () => {
const addSpy = vi.spyOn(calculator, 'add')
const result = calculator.add(2, 3)
expect(result).toBe(5) // Originalfunktion läuft
expect(addSpy).toHaveBeenCalledWith(2, 3) // Aufruf protokolliert
})
})
spyOn ist sanfter als mock – Verhalten bleibt, plus Überwachung.
Globale Objekte mocken
Keine echten HTTP-Requests in Tests? Globales fetch mocken:
vi.stubGlobal('fetch', vi.fn().mockResolvedValue({
ok: true,
json: () => Promise.resolve({ data: 'mocked' })
}))
Mit vi.stubGlobal auch window, localStorage und andere Browser-Globals.
Mocking ist oft der kniffligste Teil. Starten Sie mit vi.fn(), dann vi.mock(). Nach jedem Test Mocks bereinigen (clearAllMocks oder restoreAllMocks) – sonst vermischen sich Testzustände.
Coverage und Best Practices
Testabdeckung ist ein Qualitätsindikator. Vitest unterstützt zwei Coverage-Provider: v8 (schneller, nativ) und istanbul (breitere Kompatibilität). Meist reicht v8.
Coverage konfigurieren
In vite.config.ts:
test: {
coverage: {
provider: 'v8',
reporter: ['text', 'html', 'lcov'], // Ausgabeformate
thresholds: {
lines: 80, // Zeilenabdeckung
functions: 80, // Funktionsabdeckung
branches: 70, // Verzweigungsabdeckung
},
exclude: ['node_modules/', 'tests/', '**/*.d.ts'],
},
}
Ausführen:
vitest run --coverage
Terminal zeigt den Report, im Ordner coverage/ liegt ein HTML-Report für ungetestete Stellen.
Schwellenwerte sinnvoll setzen
thresholds sind aktiv: Unterschreitung führt zum Fehlerabbruch – nützlich in CI, um Qualität zu erzwingen.
Nicht zu hoch ansetzen. 80 % ist ein guter Start; 100 % kostet oft mehr, als es bringt.
CI/CD-Integration
In GitHub Actions oder anderer CI einen Schritt ergänzen:
# .github/workflows/test.yml
- name: Run tests with coverage
run: npm run test:run -- --coverage
Optional lcov an Codecov oder Coveralls senden, um Trends zu verfolgen.
Praktische Tipps
- Nicht 100 % anstreben: Hohe Coverage ≠ hohe Qualität. Kernlogik testen, Randfälle selektiv.
- Happy Path zuerst: Hauptfluss hat Priorität, Ausnahmen danach.
- Veraltete Tests aufräumen: Tests brauchen Wartung – Redundantes entfernen.
- Watch-Modus nutzen:
vitestbeim Entwickeln laufen lassen, Probleme sofort sehen.
Fazit
Vitest in drei Worten: schnell, unkompliziert, praxisnah.
Schnell – Cold Start 200 ms, 500 Tests in ~8 Sekunden, fast eine Größenordnung schneller als Jest. Unkompliziert – Vite-Konfiguration teilen, installieren und loslegen, ohne transform/moduleNameMapper-Chaos. Praxisnah – Jest-ähnliche API, geringe Migrationskosten.
Mit Vite? Vitest wählen – Jest-ESM-Probleme sparen Sie sich.
Noch auf Jest? Eine halbe Stunde Migration lohnt sich: Vitest installieren, Imports anpassen – oft läuft alles sofort.
TDD nicht überkomplizieren. Mit einfachen Hilfsfunktionen starten, Tests zuerst. Gewöhnt man sich daran, ist „erst denken, dann coden“ oft effizienter.
Tests sind keine Last, sondern Absicherung. Vitest einmal sauber konfigurieren – danach coden Sie ruhiger.
Vitest Unit-Test-Konfiguration und TDD-Workflow
Vollständige Schritte zur Vitest-Konfiguration und praktischer TDD-Entwicklung
⏱️ Estimated time: 30 min
- 1
Step 1: Vitest installieren
Installationsbefehl ausführen:
```bash
npm install -D vitest
```
Systemvoraussetzungen: Vite >= 6.0.0, Node >= 20.0.0 - 2
Step 2: vite.config.ts konfigurieren
Test-Feld in der Konfigurationsdatei ergänzen:
```typescript
import { defineConfig } from 'vitest/config'
export default defineConfig({
test: {
globals: true,
environment: 'node',
include: ['tests/**/*.test.ts'],
},
})
```
globals: true spart das Importieren von describe, it und expect. - 3
Step 3: Run-Skripte hinzufügen
In package.json ergänzen:
```json
{
"scripts": {
"test": "vitest",
"test:run": "vitest run"
}
}
```
npm test startet den Watch-Modus, npm run test:run führt einmal aus (für CI). - 4
Step 4: Ersten Test schreiben
Testdatei tests/math.test.ts anlegen:
```typescript
import { describe, it, expect } from 'vitest'
describe('Math', () => {
it('should add numbers', () => {
expect(1 + 1).toBe(2)
})
})
```
npm test ausführen, um die Konfiguration zu prüfen. - 5
Step 5: TDD-Workflow praktizieren
Testgetriebene Entwicklung befolgen:
• Schritt 1: Zuerst Test schreiben, erwartetes Verhalten definieren
• Schritt 2: Minimalen Code implementieren, der besteht
• Schritt 3: Grenzfälle testen
• Schritt 4: Refactoring und Optimierung
Vitest Watch-Modus liefert Feedback in Sekunden. - 6
Step 6: Coverage konfigurieren
Coverage-Konfiguration ergänzen:
```typescript
coverage: {
provider: 'v8',
reporter: ['text', 'html'],
thresholds: {
lines: 80,
functions: 80,
},
}
```
vitest run --coverage ausführen, um den Report zu erzeugen.
FAQ
Was ist der Unterschied zwischen Vitest und Jest?
Wie migriere ich von Jest zu Vitest?
• Jest-Pakete deinstallieren, vitest installieren
• jest.config.js nach vite.config.ts übertragen
• Imports in Testdateien von 'jest' auf 'vitest' ändern
• jest.fn(), jest.mock() durch vi.fn(), vi.mock() ersetzen
In den meisten Fällen ist die Migration in 30 Minuten erledigt.
Welche Testumgebungen unterstützt Vitest?
Wie mocke ich API-Anfragen in Vitest?
• vi.fn(): Einzelne Funktion mocken, Rückgabewerte vorgeben
• vi.mock(): Ganzes Modul mocken, alle Exports ersetzen
• vi.spyOn(): Echte Funktionsaufrufe überwachen, Implementierung bleibt
Nach jedem Test vi.clearAllMocks() oder vi.restoreAllMocks() aufrufen.
Wie konfiguriere ich Coverage in Vitest?
Was ist der Kern des TDD-Workflows?
• Rot: Zuerst einen fehlschlagenden Test schreiben
• Grün: Minimalen Code schreiben, der besteht
• Refactor: Code-Struktur optimieren
Mit Vitest Watch-Modus erhalten Sie nach Codeänderungen Feedback in Sekunden. Tests zuerst zu schreiben, hilft beim Klären des Funktionsdesigns.
8 Min. Lesezeit · Veröffentlicht am: 14. Apr. 2026 · Aktualisiert am: 14. Juli 2026
Vitest Testing Guide
Du liest den ersten Beitrag dieser Serie. Lies den nächsten Beitrag oder öffne die Serienübersicht, um den gesamten Pfad zu sehen.
Vorheriger
Du bist am Anfang dieser Serie.
Nächster
Vitest Unit-Tests in der Praxis: TDD-Workflow und Coverage-Konfiguration
Vitest-TDD-Praxisleitfaden: vollständiger Red-Green-Refactor-Zyklus, Coverage-Reports und CI-Schwellenwerte, vi.fn/vi.spy/vi.mock im Vergleich – das bevorzugte Frontend-Testframework 2026
Teil 2 von 3
Ähnliche Beiträge
Vitest Komponententests in der Praxis: Browser Mode und Playwright-Integration

Vitest Komponententests in der Praxis: Browser Mode und Playwright-Integration
Self-Hosted Dev Sandboxes mit Docker und Go: Preview-URLs ohne Kubernetes


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