48 Testing mit TypeScript

Das Testen von Code ist ein unverzichtbarer Schritt in jedem Softwareentwicklungsprozess. Es hilft, Fehler zu identifizieren und zu korrigieren und gewährleistet, dass der Code wie erwartet funktioniert. Es gibt verschiedene Arten von Tests, die in einem TypeScript-Projekt durchgeführt werden können:

  1. Unit-Tests: Unit-Tests konzentrieren sich auf kleinste Einheiten des Codes, wie zum Beispiel Funktionen oder Methoden. Sie prüfen, ob diese kleinen Codeeinheiten korrekt arbeiten. Für das Unit-Testing in TypeScript können Sie Bibliotheken wie Jest oder Mocha verwenden.

  2. Integrationstests: Integrationstests prüfen, ob verschiedene Teile Ihrer Anwendung korrekt zusammenarbeiten. Sie können beispielsweise testen, ob eine Funktion korrekt mit einer Datenbank interagiert oder ob ein Webserver korrekt auf Anfragen reagiert.

  3. End-to-End-Tests (E2E-Tests): E2E-Tests prüfen das Verhalten der gesamten Anwendung von Anfang bis Ende. Sie simulieren das Verhalten eines echten Benutzers und stellen sicher, dass die gesamte Anwendung wie erwartet funktioniert. Tools wie Protractor oder Cypress können für E2E-Tests verwendet werden.

  4. Snapshot-Tests: Snapshot-Tests sind eine spezielle Art von Test, die besonders nützlich in der Frontend-Entwicklung sind. Sie nehmen eine “Momentaufnahme” von einem UI-Komponentenbaum und vergleichen diese bei jedem Testlauf mit einer vorherigen Aufnahme. Dies hilft, unerwartete Änderungen an der UI zu erkennen.

  5. Typ-Tests: Da TypeScript eine typisierte Sprache ist, kann die Überprüfung der Typen einen weiteren Level an Sicherheit hinzufügen. TypeScript bietet bereits eingebaute Möglichkeiten zur Typüberprüfung, aber zusätzliche Tools wie dtslint können verwendet werden, um sicherzustellen, dass Ihre Typdefinitionen korrekt sind.

Bei der Erstellung Ihrer Tests ist es hilfreich, die Prinzipien des Test-Driven Development (TDD) zu beachten. TDD bedeutet, dass Sie zuerst einen fehlschlagenden Test schreiben, dann den Code schreiben, der den Test bestehen lässt, und schließlich den Code refaktorisieren. TDD kann dazu beitragen, sauberen und fehlerfreien Code zu schreiben und sicherzustellen, dass alle Funktionen Ihrer Anwendung ordnungsgemäß getestet werden.

48.1 Unit-Tests

Unit-Tests sind eine Methode zur Überprüfung der Korrektheit des Codes auf der niedrigsten, isolierten Ebene. Ein Unit-Test konzentriert sich auf eine einzige “Einheit” des Codes, die unabhängig vom Rest des Systems getestet werden kann. In der Regel ist eine Einheit eine einzelne Funktion oder Methode.

Ein guter Unit-Test ist in der Regel einfach, schnell und isoliert. Er testet nur eine Sache und hat keine Abhängigkeiten zu anderen Tests.

Für TypeScript und JavaScript gibt es eine Reihe von Test-Frameworks und -Bibliotheken, die für das Schreiben von Unit-Tests verwendet werden können. Dazu gehören unter anderem:

  1. Jest: Jest ist eine beliebte Test-Bibliothek für JavaScript, die von Facebook entwickelt wurde. Sie bietet eine Vielzahl von Funktionen, darunter die Möglichkeit, Tests in parallelen Prozessen auszuführen, Snapshot-Tests zu schreiben und Tests mit simulierten Abhängigkeiten (sog. “Mocks”) durchzuführen.

  2. Mocha: Mocha ist ein weiteres beliebtes Test-Framework, das in vielen JavaScript- und TypeScript-Projekten verwendet wird. Es ist flexibel und konfigurierbar und ermöglicht es Ihnen, Tests mit jeder Assertion-Bibliothek zu schreiben, die Sie bevorzugen.

  3. Jasmine: Jasmine ist ein Behavior-Driven-Development (BDD)-Framework für JavaScript, das keine externen Abhängigkeiten hat. Es bietet Funktionen für Testdoubles wie Spies und Stubs, die das Schreiben von Unit-Tests erleichtern.

Hier ist ein einfaches Beispiel, wie ein Unit-Test mit Jest in TypeScript aussehen könnte:

import { sum } from './sum';

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

In diesem Beispiel haben wir eine sum-Funktion, die zwei Zahlen addiert. Der Unit-Test überprüft, ob das Ergebnis dieser Funktion korrekt ist. Wenn die Funktion sum(1, 2) den Wert 3 zurückgibt, besteht der Test. Wenn nicht, schlägt der Test fehl.

Der Vorteil von Unit-Tests ist, dass sie oft sehr schnell sind und das Auffinden von Fehlern erleichtern, da sie nur einen sehr spezifischen Teil des Codes testen.

48.2 Integration Tests

Integrationstests dienen dazu, das Zusammenspiel mehrerer Komponenten einer Anwendung zu überprüfen. Sie sind ein zentraler Bestandteil des Testens, da sie dabei helfen, Fehler zu erkennen, die auftreten können, wenn verschiedene Teile der Software zusammenarbeiten. Während Unit-Tests darauf abzielen, die Funktion einer einzelnen Komponente zu überprüfen, konzentrieren sich Integrationstests auf die Kommunikation zwischen den Komponenten.

In einem TypeScript-Projekt könnten diese Komponenten Funktionen, Klassen oder Module sein. Beispielsweise könnten Sie einen Integrationstest schreiben, um zu überprüfen, ob eine Funktion, die Daten aus einer Datenbank abruft, korrekt mit der Funktion zusammenarbeitet, die diese Daten analysiert.

Ein Beispiel für einen Integrationstest könnte sein, einen Test zu schreiben, der einen HTTP-Endpunkt Ihrer Anwendung trifft und dann überprüft, ob die richtige Antwort zurückgegeben wird.

In TypeScript und JavaScript können Sie für Integrationstests dieselben Frameworks verwenden wie für Unit-Tests. Hier sind einige Beispiele:

  1. Jest: Jest ist ein vielseitiges Test-Framework, das sowohl für Unit- als auch für Integrationstests verwendet werden kann.

  2. Mocha/Chai: Mocha ist ein Test-Framework, und Chai ist eine zugehörige Bibliothek für Assertions. Zusammen können sie verwendet werden, um Integrationstests zu schreiben.

  3. Supertest: Supertest ist eine Bibliothek für Node.js, die speziell für das Testen von HTTP entwickelt wurde. Mit Supertest können Sie Integrationstests schreiben, die HTTP-Endpunkte Ihrer Anwendung aufrufen und die Antworten überprüfen.

  4. Cypress: Cypress ist ein End-to-End-Test-Framework, das auch für Integrationstests genutzt werden kann. Es ermöglicht automatisierte Tests, die in einem Browser ausgeführt werden und die Interaktionen eines Benutzers mit Ihrer Anwendung simulieren.

Integrationstests sind ein wichtiger Schritt, um sicherzustellen, dass Ihre Anwendung als Ganzes korrekt funktioniert. Sie können dabei helfen, Fehler zu finden, die in Unit-Tests möglicherweise nicht entdeckt werden, da diese Fehler erst auftreten, wenn verschiedene Teile Ihrer Anwendung zusammenarbeiten.

48.3 E2E Tests mit Cypress

End-to-End-Tests (E2E-Tests) sind darauf ausgelegt, das gesamte System oder die gesamte Anwendung von Anfang bis Ende zu testen, um sicherzustellen, dass sie in einer Weise arbeitet, die ein Benutzer erwarten würde. Im Gegensatz zu Unit- und Integrationstests, die sich auf spezifische Teile der Codebasis konzentrieren, simulieren E2E-Tests reale Benutzerinteraktionen und überprüfen die gesamte Anwendung auf korrekte Funktion.

Cypress ist ein beliebtes Framework für das Schreiben von E2E-Tests in JavaScript und TypeScript. Es ermöglicht Ihnen, Tests zu schreiben, die in einem echten Browser ausgeführt werden und die Interaktionen eines Benutzers mit Ihrer Anwendung simulieren.

Hier ist ein grundlegendes Beispiel für einen E2E-Test mit Cypress:

describe('My First Test', () => {
  it('Visits the Kitchen Sink', () => {
    // Besuchen Sie eine Seite
    cy.visit('https://example.cypress.io')

    // Finden Sie ein Element mit dem Inhalt 'type' 
    // und klicken Sie darauf
    cy.contains('type').click()  

    // Finden Sie ein Eingabeelement mit dem Attribut 
    // `name` gleich `email` und geben Sie einen Text ein
    cy.get('[name=email]').type('fake@email.com')

    // Stellen Sie sicher, dass der eingegebene Text korrekt ist
    cy.get('[name=email]').should('have.value', 'fake@email.com')
  })
})

In diesem Beispiel besucht der Test zuerst eine Webseite, findet und klickt auf ein Element, gibt Text in ein Eingabefeld ein und überprüft dann, ob der eingegebene Text korrekt ist. Es simuliert die Aktionen, die ein Benutzer möglicherweise auf Ihrer Webseite ausführen würde.

Einige Vorteile der Verwendung von Cypress sind:

  1. Echtzeit-Reloads: Cypress führt Tests automatisch neu aus, sobald Sie Änderungen an Ihren Testdateien vornehmen.

  2. Time-Travelling: Cypress nimmt Snapshots auf, während Ihre Tests laufen. Dies ermöglicht Ihnen, den Zustand Ihrer Anwendung zu jedem Zeitpunkt im Test zu überprüfen.

  3. Automatisierte Wartezeiten: Cypress wartet automatisch auf Befehle und Assertions, bis sie erfolgreich sind.

  4. Einfache Debugging: Cypress ermöglicht es Ihnen, direkt in den Entwicklertools Ihres Browsers zu debuggen. Es macht auch die Nutzung von .debug() zur Anhaltung der Ausführung von Tests zu irgendeinem Zeitpunkt möglich.

Cypress bietet eine umfassende Lösung für das automatisierte Testen von Frontend-Anwendungen und eignet sich hervorragend für E2E-Tests.

48.4 Vollständiges Jest Beispiel

Hier ist ein vollständiges Beispiel, das zeigt, wie man ein einfaches TypeScript-Projekt mit Unit-Tests unter Verwendung von Jest einrichtet:

48.4.1 Schritt 1: Projekt Setup

Zuerst erstellen Sie ein neues Verzeichnis für Ihr Projekt und initialisieren ein neues npm-Projekt:

mkdir mein-ts-projekt
cd mein-ts-projekt
npm init -y

48.4.2 Schritt 2: TypeScript und Jest installieren

Installieren Sie TypeScript, Jest und die zugehörigen Typen sowie den TypeScript-Compiler für Jest:

npm install --save-dev typescript jest @types/jest ts-jest

48.4.3 Schritt 3: TypeScript-Konfiguration

Erstellen Sie eine tsconfig.json-Datei für TypeScript:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "esModuleInterop": true,
    "outDir": "./dist",
    "strict": true,
    "forceConsistentCasingInFileNames": true
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

48.4.4 Schritt 4: Jest-Konfiguration

Konfigurieren Sie Jest, indem Sie die jest.config.js-Datei erstellen:

module.exports = {
  preset: 'ts-jest',
  testEnvironment: 'node',
};

48.4.5 Schritt 5: Erstellen Sie ein Test-Skript

Fügen Sie in Ihrer package.json ein Skript hinzu, um Jest zu starten:

"scripts": {
  "test": "jest"
}

48.4.6 Schritt 6: Schreiben Sie den Code und die Tests

Erstellen Sie einen src-Ordner und schreiben Sie Ihren TypeScript-Code. Zum Beispiel src/sum.ts:

export function sum(a: number, b: number): number {
  return a + b;
}

Schreiben Sie dann einen Test dafür. Erstellen Sie einen tests-Ordner und fügen Sie eine Datei sum.test.ts hinzu:

import { sum } from '../src/sum';

test('adds 1 + 2 to equal 3', () => {
  expect(sum(1, 2)).toBe(3);
});

48.4.7 Schritt 7: Ausführen der Tests

Führen Sie Ihre Tests mit dem folgenden Befehl aus:

npm test

Dies wird Jest ausführen, der Ihre Tests in der tests-Datei findet und ausführt.

48.4.8 Vollständige package.json

Hier ist ein Beispiel für eine vollständige package.json:

{
  "name": "mein-ts-projekt",
  "version": "1.0.0",
  "scripts": {
    "test": "jest"
  },
  "devDependencies": {
    "@types/jest": "^26.0.0",
    "jest": "^26.0.0",
    "ts-jest": "^26.0.0",
    "typescript": "^4.0.0"
  },
  "jest": {
    "preset": "ts-jest",
    "testEnvironment": "node"
  }
}

Mit diesem Setup haben Sie ein grundlegendes TypeScript-Projekt mit Jest-Tests erstellt. Sie können Ihren Code in src schreiben und Ihre Tests in tests. Führen Sie npm test aus, um Ihre Jest-Tests auszuführen.