Unit-Testing mit Vitest
Dauer: 45 Minuten
Ziel: Zentrale Logik mit schnellen, verlässlichen Unit-Tests absichern
Was sind Unit-Tests?
Unit-Tests sind automatisierte Tests, die einzelne Funktionen oder Module isoliert prüfen. Sie stellen sicher, dass der Code korrekt funktioniert — auch nach Änderungen.
Vorteile von Unit-Tests:
- Fehler werden frühzeitig erkannt
- Änderungen am Code können sicher durchgeführt werden
- Tests dokumentieren das erwartete Verhalten
- KI-generierter Code kann systematisch überprüft werden
Für unser TUI-Projekt sind Unit-Tests die wichtigste Teststufe nach Type Checking und Linting.
Es gibt viele Möglichkeiten, Unit-Tests zu schreiben. Im JavaScript-/TypeScript-Umfeld gibt es vor allem zwei gängige Frameworks:
Wir verwenden Vitest — für unser TUI-Projekt mit TypeScript sprechen mehrere Gründe dafür:
- Erstklassiger TypeScript-Support — Vitest versteht TypeScript
ohne zusätzliche Konfiguration. Jest benötigt dafür extra Pakete
wie
ts-jestoder@swc/jest, was im Workshop unnötige Komplexität bedeutet. - Schnelle Ausführung — Vitest nutzt Vite unter der Haube und ist dadurch deutlich schneller als Jest.
- Jest-kompatible API —
describe,it,expectfunktionieren wie bei Jest. Das Wissen ist direkt übertragbar und in vielen Projekten und Webapps verbreitet. - Einfaches Setup —
npm install -D vitestund ein Script inpackage.jsonreichen aus.
Node.js bringt seit Version 18 einen eingebauten Test Runner mit
(node:test), der ohne zusätzliche
Pakete funktioniert und für einfache Setups völlig ausreicht.
Die Konzepte sind dabei dieselben: Tests werden mit describe und it
strukturiert, und Erwartungen mit Assertions geprüft. Wer node:test
kennt, findet sich in Vitest sofort zurecht — und umgekehrt.
Mehr dazu in der optionalen Lektion zu node:test.
Setup mit Vitest
npm install -D vitestIn package.json:
{
"scripts": {
"test": "vitest run",
"test:watch": "vitest"
}
}Beispiel: Reine Funktion testen
Vitest-Tests nutzen drei zentrale Funktionen:
| Funktion | Bedeutung |
|---|---|
describe('...', () => {}) | Gruppiert zusammengehörige Tests |
it('...', () => {}) | Definiert einen einzelnen Testfall |
expect(wert) | Startet eine Prüfung (Assertion), z.B. expect(wert).toBe(42) |
Beispiel mit TypeScript-Dateien:
src/temperature.ts:
export function celsiusToFahrenheit(celsius: number): number {
// Defensiver Check: zur Laufzeit relevant, falls die Funktion
// aus ungetyptem JavaScript aufgerufen wird
if (typeof celsius !== 'number' || Number.isNaN(celsius)) {
throw new Error('Ungültige Temperatur');
}
return (celsius * 9) / 5 + 32;
}src/temperature.test.ts:
import { describe, expect, it } from 'vitest';
import { celsiusToFahrenheit } from './temperature.ts';
describe('celsiusToFahrenheit', () => {
it('rechnet 0°C in 32°F um', () => {
// Arrange — Testdaten vorbereiten
const celsius = 0;
// Act — Funktion aufrufen
const result = celsiusToFahrenheit(celsius);
// Assert — Ergebnis prüfen
expect(result).toBe(32);
});
it('rechnet 100°C in 212°F um', () => {
expect(celsiusToFahrenheit(100)).toBe(212);
});
it('wirft Fehler bei NaN-Eingabe', () => {
expect(() => celsiusToFahrenheit(Number.NaN)).toThrow('Ungültige Temperatur');
});
});Der erste Test zeigt das AAA-Schema ausführlich mit Kommentaren. Bei einfachen Fällen (zweiter und dritter Test) darf alles in einer Zeile stehen.
Tests ausführen
Tests ausführen mit npm test (Kurzform von npm run test):
npm testBeispielausgabe (kann je nach Umgebung leicht abweichen):
✓ src/temperature.test.ts (3 tests) 5ms
✓ celsiusToFahrenheit > rechnet 0°C in 32°F um
✓ celsiusToFahrenheit > rechnet 100°C in 212°F um
✓ celsiusToFahrenheit > wirft Fehler bei NaN-Eingabe
Test Files 1 passed (1)
Tests 3 passed (3)Watch Mode
Mit npm run test:watch startet Vitest im Watch-Modus.
Tests laufen dann bei Dateiänderungen automatisch neu.
Das ist praktisch während der Entwicklung.
Für einen einmaligen Check (z. B. vor Commit) nutzen wir npm test.
Beenden: q oder Ctrl+C.
Wichtige expect-Methoden
| Methode | Beschreibung |
|---|---|
expect(a).toBe(b) | Prüft ob a === b |
expect(a).not.toBe(b) | Prüft ob a !== b |
expect(a).toEqual(b) | Prüft ob Objekte/Arrays inhaltlich gleich sind |
expect(fn).toThrow() | Prüft ob fn einen Fehler wirft (optional mit erwarteter Nachricht, z.B. toThrow('msg')) |
expect(a).toBeTruthy() | Prüft ob a truthy ist |
expect(a).toContain(b) | Prüft ob Array/String b enthält |
Dateikonvention
Testdateien werden neben der getesteten Datei abgelegt
und erhalten die Endung .test.js (bzw. .test.ts mit TypeScript):
src/
temperature.ts
temperature.test.ts
weather-api.ts
weather-api.test.tsGute Praxis für Unit-Tests
- Testnamen klar formulieren (erwartetes Verhalten nennen)
- AAA-Schema verwenden: Arrange (Testdaten vorbereiten), Act (Funktion aufrufen), Assert (Ergebnis prüfen)
- Nur eine konkrete Erwartung pro Verhalten testen
- Vor allem pure Funktionen testen (ohne I/O, ohne globale Seiteneffekte)
- Verhalten und Anforderungen testen, nicht nur Implementierungsdetails
- KI-generierte Tests kritisch prüfen (decken sie echte Risiken oder nur den Happy Path ab?)
Tipp: KI-Coding-Agents im Test-Workflow
- KI-Coding-Agents können Tests direkt ausführen (z. B.
npm run testodernpm run test:watch). - Sie können auch Testabdeckung prüfen (z. B.
npx vitest run --coverage, wenn Coverage im Projekt eingerichtet ist). - Sie können Tests generieren — entweder explizit auf Prompt oder als vorgegebene Teilaufgabe im Workflow (z. B. vor Commit).
TDD (Test-Driven Development)
Eine verbreitete Technik: zuerst einen fehlschlagenden Test schreiben, dann den minimalen Code, der den Test bestehen lässt, anschließend refactoren. So entstehen Tests, die echte Anforderungen prüfen.
Als nächster Schritt: ein konkreter Integrationscheck im TUI mit 1 Erfolgs- und 1 Fehlerfall.
Die weiteren Qualitätsstufen (u. a. E2E, CI/CD) folgen im Ausblick.