Einleitung: Warum Testing in TypeScript ein Must-have ist
TypeScript ist ein leistungsstarkes Werkzeug, das JavaScript um statische Typisierung erweitert. Allerdings garantiert selbst das strengste Typsystem nicht, dass Ihr Code zur Laufzeit korrekt funktioniert. Bugs, logische Fehler, fehlerhafte Behandlung von Randfällen – all das bleibt außerhalb der Kompilierung. Hier kommt das Testen ins Spiel.
In diesem Artikel werden wir drei grundlegende Testebenen in TypeScript behandeln: Unit-Tests, Integrationstests und E2E-Tests (End-to-End). Sie erfahren, wie sie sich unterscheiden, wann welche anzuwenden ist, und sehen reale Codebeispiele. Nach der Lektüre können Sie eine vollständige Test-Pipeline für Ihr TypeScript-Projekt einrichten.
1. Unit-Tests: Überprüfung atomarer Code-Einheiten
Unit-Tests sind die schnellste und günstigste Testebene. Sie isolieren eine einzelne Funktion, Methode oder Klasse und testen sie unabhängig von externen Abhängigkeiten (Datenbanken, APIs, Dateisystem).
1.1 Werkzeugauswahl und Einrichtung
Das beliebteste Framework für TypeScript ist Jest. Es bietet integrierte TypeScript-Unterstützung über ts-jest oder @swc/jest.
// Installationnpm install --save-dev jest @types/jest ts-jest
// Konfigurationsinitialisierungnpx ts-jest config:initDanach erscheint im Stammverzeichnis die Datei jest.config.js mit den Einstellungen.
1.2 Beispiel eines Unit-Tests für eine Funktion
Angenommen, wir haben eine einfache Funktion zur Berechnung eines Rabatts:
// discount.tsexport function applyDiscount(price: number, discountPercent: number): number { if (discountPercent < 0 || discountPercent > 100) { throw new Error('Ungültiger Rabattprozentsatz'); } return price - (price * discountPercent) / 100;}Schreiben wir einen Unit-Test dafür:
// discount.test.tsimport { applyDiscount } from './discount';
describe('applyDiscount', () => { it('sollte einen Rabatt von 10% korrekt berechnen', () => { expect(applyDiscount(1000, 10)).toBe(900); });
it('sollte den ursprünglichen Preis bei 0% Rabatt zurückgeben', () => { expect(applyDiscount(500, 0)).toBe(500); });
it('sollte einen Fehler bei einem Rabatt über 100% werfen', () => { expect(() => applyDiscount(100, 150)).toThrow('Ungültiger Rabattprozentsatz'); });});Ausführen mit: npx jest. Alle drei Tests sollten grün durchlaufen.
1.3 Mocks und Stubs
Wenn eine Funktion auf einen externen Dienst zugreift (z. B. eine Datenbank), ersetzen wir ihn durch einen Mock:
// userService.test.tsimport { getUser } from './userService';import { db } from './database';
jest.mock('./database'); // Automatischer Mock
test('getUser gibt einen Benutzer anhand der ID zurück', async () => { (db.findUser as jest.Mock).mockResolvedValue({ id: 1, name: 'Ivan' }); const user = await getUser(1); expect(user.name).toBe('Ivan');});2. Integrationstests: Überprüfung der Modulinteraktion
Integrationstests prüfen, wie verschiedene Teile des Systems zusammenarbeiten: Controller, Services, Repositories, Datenbanken. Sie sind langsamer als Unit-Tests, geben aber mehr Vertrauen in die Korrektheit der Verbindungen.
2.1 Testen eines API-Endpunkts mit Express
Zum Testen von HTTP-Anfragen verwenden wir die Bibliothek Supertest zusammen mit Jest.
// Installationnpm install --save-dev supertest @types/supertestAngenommen, wir haben eine Express-Anwendung:
// app.tsimport express from 'express';import { createUser } from './controllers/userController';
export const app = express();app.use(express.json());app.post('/api/users', createUser);Integrationstest:
// app.test.tsimport request from 'supertest';import { app } from './