Die Bibliothek conditional-type-checks für TypeScript: Typen testen leicht gemacht
TypeScript ist eine mächtige Sprache mit einem komplexen Typsystem, das bedingte Typen, Mapping, Typinferenz und vieles mehr umfasst. Doch Entwickler stehen oft vor einem Problem: Wie lässt sich überprüfen, ob die Typen genau wie erwartet funktionieren? Herkömmliche Unit-Tests prüfen Werte zur Laufzeit, können aber die Korrektheit der Typen zur Kompilierzeit nicht garantieren. Genau hier kommt die Bibliothek conditional-type-checks ins Spiel.
Diese Bibliothek bietet eine Reihe von Hilfsmitteln für das statische Testen von TypeScript-Typen. Sie ermöglicht es, Tests zu schreiben, die prüfen, ob ein bestimmter Typ den Erwartungen entspricht: Ist er ein Subtyp eines anderen? Sind zwei Typen gleich? Kann ein Wert einem anderen zugewiesen werden? Alle Prüfungen finden zur Kompilierzeit statt und haben keinen Einfluss auf die Laufzeit.
Warum ist das nützlich? Erstens für das Refactoring komplexer Typen – Sie können sicher sein, dass Änderungen das erwartete Verhalten nicht beeinträchtigen. Zweitens zur Dokumentation Ihrer Absichten – Typtests dienen als lebendige Spezifikation. Drittens für Bibliotheken, die komplexe Typen bereitstellen – Ihre Nutzer können auf deren Korrektheit vertrauen. Die Bibliothek ist besonders hilfreich bei der Arbeit mit bedingten Typen (extends, infer), Template-Literal-Typen und rekursiven Typen.
Installation
Die Bibliothek wird über npm als Entwicklungsabhängigkeit installiert:
npm install --save-dev conditional-type-checks
Oder über yarn:
yarn add --dev conditional-type-checks
Es sind keine zusätzlichen TypeScript-Konfigurationen erforderlich – die Bibliothek besteht vollständig aus Typen und generiert keinen JavaScript-Code.
Schnellstart
Erstellen Sie eine Datei type-tests.ts und schreiben Sie einen einfachen Test:
import { IsExact } from 'conditional-type-checks';
type TestType = string;
type ExpectedType = string;
// Prüfen, ob die Typen exakt übereinstimmen
type TestPass = IsExact<TestType, ExpectedType>; // true
// Bei Nichtübereinstimmung gibt es einen Kompilierfehler
// type TestFail = IsExact<TestType, number>; // false
Damit der Test tatsächlich etwas prüft, verwenden Sie die Konstruktion expectType oder einfach eine Zuweisung mit Prüfung:
import { assert } from 'conditional-type-checks';
type MyType = { name: string; age: number };
// Prüfen, ob MyType ein Objekt mit den Feldern name und age ist
type Check = assert<IsExact<MyType, { name: string; age: number }>>; // OK
Wenn die Prüfung fehlschlägt, gibt TypeScript einen Kompilierfehler mit der Meldung Type 'false' does not satisfy the constraint 'true' aus.
Wichtige Methoden und Funktionen
Die Bibliothek bietet mehrere zentrale Hilfsmittel. Sehen wir uns jedes im Detail an.
1. IsExact<T, U>
Signatur: type IsExact<T, U> = ...
Funktion: Prüft, ob die Typen T und U exakt identisch sind. Gibt true zurück, wenn die Typen identisch sind, andernfalls false. Berücksichtigt alle TypeScript-Aspekte wie readonly, optional und andere Modifikatoren.
import { IsExact } from 'conditional-type-checks';
type A = { readonly x: number };
type B = { x: number };
type Test1 = IsExact<A, B>; // false – readonly unterscheidet sich
type Test2 = IsExact<string, string>; // true
type Test3 = IsExact<string | number, number | string>; // true – Reihenfolge irrelevant
type Test4 = IsExact<{ a: string }, { a: string; b?: number }>; // false
2. IsAny<T>
Signatur: type IsAny<T> = ...
Funktion: Prüft, ob T vom Typ any ist. Dies ist besonders nützlich, um versehentliche Verwendung von any in Ihrem Code zu erkennen.
import { IsAny } from 'conditional-type-checks';
type Test1 = IsAny<any>; // true
type Test2 = IsAny<string>; // false
type Test3 = IsAny<unknown>; // false
3. IsNever<T>
Signatur: type IsNever<T> = ...
Funktion: Prüft, ob T vom Typ never ist. Nützlich, um zu testen, ob bedingte Typen korrekt zu never aufgelöst werden.
import { IsNever } from 'conditional-type-checks';
type Test1 = IsNever<never>; // true
type Test2 = IsNever<string>; // false
type Test3 = IsNever<string | never>; // false – string | never wird zu string
4. IsUnknown<T>
Signatur: type IsUnknown<T> = ...
Funktion: Prüft, ob T vom Typ unknown ist. Hilfreich, um sicherzustellen, dass Ihre Typen korrekt mit unknown umgehen.
import { IsUnknown } from 'conditional-type-checks';
type Test1 = IsUnknown<unknown>; // true
type Test2 = IsUnknown<any>; // false
type Test3 = IsUnknown<string>; // false
5. IsUnion<T>
Signatur: type IsUnion<T> = ...
Funktion: Prüft, ob T ein Union-Typ ist (also aus mehr als einem Typ besteht). Nützlich für Tests mit bedingten Typen, die sich je nach Union-Mitgliedern unterschiedlich verhalten.
import { IsUnion } from 'conditional-type-checks';
type Test1 = IsUnion<string | number>; // true
type Test2 = IsUnion<string>; // false
type Test3 = IsUnion<never>; // false
6. Has<T, U>
Signatur: type Has<T, U> = ...
Funktion: Prüft, ob T den Typ U als Bestandteil enthält (z. B. ob ein Union-Typ einen bestimmten Typ enthält).
import { Has } from 'conditional-type-checks';
type Test1 = Has<string | number, string>; // true
type Test2 = Has<string | number, boolean>; // false
type Test3 = Has<{ a: string }, { a: string }>; // true
7. assert<T extends true>
Signatur: type assert<T extends true> = ...
Funktion: Eine Hilfsfunktion, die einen Kompilierfehler auslöst, wenn das übergebene Typ-Argument nicht true ist. Ideal, um Typprüfungen in Ihren Tests zu erzwingen.
import { assert, IsExact } from 'conditional-type-checks';
type MyType = { name: string; age: number };
// Diese Prüfung besteht
type Check1 = assert<IsExact<MyType, { name: string; age: number }>>;
// Diese Prüfung würde einen Fehler auslösen
// type Check2 = assert<IsExact<MyType, { name: string }>>; // Fehler!
Praktische Anwendungsbeispiele
Sehen wir uns einige reale Szenarien an, in denen conditional-type-checks besonders nützlich ist.
Beispiel 1: Testen von bedingten Typen
import { IsExact, assert } from 'conditional-type-checks';
// Ein bedingter Typ, der den Rückgabetyp einer Funktion extrahiert
type ReturnTypeOf<T> = T extends (...args: any[]) => infer R ? R : never;
// Tests
type Test1 = assert<IsExact<ReturnTypeOf<() => string>, string>>;
type Test2 = assert<IsExact<ReturnTypeOf<(x: number) => boolean>, boolean>>;
type Test3 = assert<IsExact<ReturnTypeOf<string>, never>>; // string ist keine Funktion
Beispiel 2: Testen von API-Typen
import { IsExact, assert } from 'conditional-type-checks';
// Angenommen, Sie haben eine API-Funktion
declare function fetchUser(id: number): Promise<{ name: string; email: string }>;
// Testen Sie den Rückgabetyp
type FetchUserReturn = ReturnType<typeof fetchUser>;
type Test = assert<IsExact<FetchUserReturn, Promise<{ name: string; email: string }>>>;
Beispiel 3: Testen von generischen Komponenten
import { IsExact, assert } from 'conditional-type-checks';
// Eine generische Funktion
function identity<T>(value: T): T {
return value;
}
// Testen Sie die Typinferenz
type Result1 = ReturnType<typeof identity<string>>;
type Test1 = assert<IsExact<Result1, string>>;
type Result2 = ReturnType<typeof identity<number>>;
type Test2 = assert<IsExact<Result2, number>>;
Häufige Fehler und Best Practices
Hier sind einige Tipps, um das Beste aus conditional-type-checks herauszuholen:
- Tests immer zur Kompilierzeit ausführen: Da die Bibliothek nur Typen prüft, müssen Sie Ihre Tests nicht ausführen – ein erfolgreicher Kompilierungsvorgang bedeutet, dass alle Tests bestanden sind.
- Kombinieren Sie mit
assert: Verwenden Sieassert, um sicherzustellen, dass Ihre Prüfungen tatsächlich durchgeführt werden. Ohneassertkönnte ein fehlgeschlagener Test unbemerkt bleiben. - Testen Sie Randfälle: Testen Sie nicht nur den Erfolgsfall, sondern auch, was passiert, wenn Typen nicht übereinstimmen. So stellen Sie sicher, dass Ihre bedingten Typen korrekt funktionieren.
- Dokumentieren Sie Ihre Typen: Nutzen Sie die Tests als lebendige Dokumentation. Jeder Test erklärt, wie ein Typ verwendet werden soll und was erwartet wird.
Fazit
Die Bibliothek conditional-type-checks ist ein unverzichtbares Werkzeug für jeden TypeScript-Entwickler, der mit komplexen Typen arbeitet. Sie ermöglicht es, Typen statisch zu testen, Fehler frühzeitig zu erkennen und die Codequalität zu verbessern. Ob Sie an einer Bibliothek arbeiten, die komplexe generische Typen bereitstellt, oder einfach nur sicherstellen möchten, dass Ihre eigenen Typen korrekt sind – diese Bibliothek macht die Arbeit mit TypeScript-Typen zuverlässiger und angenehmer.
Probieren Sie es noch heute aus und integrieren Sie Typtests in Ihre Entwicklungsworkflow. Ihre zukünftigen Ich (und Ihre Kollegen) werden es Ihnen danken!
Auch in der Bibliothek
Datenvalidierung in JavaScript (Node.js): Joi, Yup, Zod
Poco C++: Das umfassende Tutorial mit Praxisbeispielen
Autorisierung und Authentifizierung in C++: Bibliotheken, Beispiele und Implementierung
Arbeit mit XML in C++: Bibliotheken PugiXML und RapidXML