Testen in TypeScript: von Unit bis E2E — vollständiger Leitfaden
\nModerne Webentwicklung ist ohne qualitativ hochwertiges Testen undenkbar. TypeScript, als Erweiterung von JavaScript, bietet leistungsstarke Werkzeuge für die Typisierung, löst jedoch nicht das Problem logischer Fehler. In diesem Artikel betrachten wir drei Testebenen: Unit (Modultests), Integration (Integrationstests) und E2E (End-to-End-Tests). Sie erfahren, wie Sie die Umgebung einrichten, effektive Tests in TypeScript schreiben und diese in eine CI/CD-Pipeline integrieren.
\n\n1. Unit-Testing: Grundlage der Testpyramide
\nUnit-Tests überprüfen isolierte Module (Funktionen, Klassen, Methoden) ohne externe Abhängigkeiten. Für TypeScript ist Jest die beste Wahl – es unterstützt TypeScript von Haus aus und bietet eine umfangreiche API für Mocks und Assertions.
\n\nEinrichtung von Jest für TypeScript
\nnpm install --save-dev jest @types/jest ts-jest\nnpx ts-jest config:init\nDie Datei jest.config.ts sollte Folgendes enthalten:
export default {\n preset: 'ts-jest',\n testEnvironment: 'node',\n roots: ['<rootDir>/src'],\n testMatch: ['**/__tests__/**/*.test.ts']\n};\n\nBeispiel eines Unit-Tests für eine Funktion
\nAngenommen, wir haben eine Funktion zur Berechnung eines Rabatts:
\n// src/utils/discount.ts\nexport function calculateDiscount(price: number, percent: number): number {\n if (percent < 0 || percent > 100) throw new Error('Invalid percent');\n return price - (price * percent) / 100;\n}\nDer Test dafür:
\n// src/__tests__/discount.test.ts\nimport { calculateDiscount } from '../utils/discount';\n\ndescribe('calculateDiscount', () => {\n it('sollte 80 für einen Preis von 100 und einen Rabatt von 20% zurückgeben', () => {\n expect(calculateDiscount(100, 20)).toBe(80);\n });\n\n it('sollte einen Fehler bei ungültigem Prozentsatz auslösen', () => {\n expect(() => calculateDiscount(100, 150)).toThrow('Invalid percent');\n });\n\n it('sollte 0 bei einem Rabatt von 100% zurückgeben', () => {\n expect(calculateDiscount(50, 100)).toBe(0);\n });\n});\n\nArbeiten mit Mocks
\nVerwenden Sie Mocks zur Isolation von Tests. Beispiel mit einem Service, der von einer Datenbank abhängt:
\n// src/services/user.service.ts\nimport { db } from '../database';\n\nexport class UserService {\n async getUser(id: number) {\n const user = await db.findUser(id);\n if (!user) throw new Error('User not found');\n return user;\n }\n}\n// src/__tests__/user.service.test.ts\nimport { UserService } from '../services/user.service';\nimport { db } from '../database';\n\njest.mock('../database');\n\ndescribe('UserService', () => {\n it('sollte einen Benutzer zurückgeben', async () => {\n const mockUser = { id: 1, name: 'Alice' };\n (db.findUser as jest.Mock).mockResolvedValue(mockUser);\n\n const service = new UserService();\n const result = await service.getUser(1);\n expect(result).toEqual(mockUser);\n });\n});\n\n2. Integrationstests: Überprüfung der Komponenteninteraktion
\nIntegrationstests überprüfen, wie Module zusammenarbeiten: API-Routen, Datenbanken, externe Dienste. Für TypeScript-Anwendungen auf Node.js wird häufig Supertest in Verbindung mit Jest verwendet.
\n\nEinrichtung eines Integrationstests für Express/NestJS
\nnpm install --save-dev supertest @types/supertest\nBeispiel eines Tests für eine REST-API (NestJS):
\n// src/__tests__/app.integration.test.ts\nimport { Test, TestingModule } from '@nestjs/testing';\nimport { INestApplication } from '@nestjs/common';\nimport * as request from 'supertest';\nimport { AppModule } from '../app.module';\n\ndescribe('AppController (e2e)', () => {\n let app: INestApplication;\n\n beforeAll(async () => {\n const moduleFixture: TestingModule = await Test.createTestingModule({\n imports: [AppModule],\n }).compile();\n\n a