Librería conditional-type-checks para TypeScript: testing de tipos
TypeScript es un lenguaje potente con un sistema de tipos complejo que incluye tipos condicionales, mapeo, inferencia de tipos y mucho más. Sin embargo, los desarrolladores a menudo se enfrentan al problema: ¿cómo verificar que los tipos funcionan exactamente como se espera? Las pruebas unitarias tradicionales verifican valores en tiempo de ejecución, pero no pueden garantizar la corrección de los tipos en tiempo de compilación. Aquí es donde entra en juego la librería conditional-type-checks.
Esta librería proporciona un conjunto de utilidades para el testing estático de tipos en TypeScript. Permite escribir pruebas que verifican si un tipo específico cumple con las expectativas: si es un subtipo de otro, si dos tipos son iguales, si un valor se puede asignar a otro, etc. Todas las comprobaciones se realizan en tiempo de compilación, sin afectar al runtime.
¿Para qué sirve esto? En primer lugar, para refactorizar tipos complejos: puedes estar seguro de que los cambios no romperán el comportamiento esperado. En segundo lugar, para documentar intenciones: las pruebas de tipos actúan como una especificación viva. En tercer lugar, para librerías que ofrecen tipos complejos: los usuarios confiarán en su corrección. La librería es especialmente útil al trabajar con tipos condicionales (extends, infer), literales de plantilla y tipos recursivos.
Instalación
La librería se instala a través de npm como dependencia de desarrollo:
npm install --save-dev conditional-type-checks
O mediante yarn:
yarn add --dev conditional-type-checks
No se requiere ninguna configuración adicional de TypeScript: la librería está compuesta completamente por tipos y no genera código JavaScript.
Inicio rápido
Crea un archivo type-tests.ts y escribe una prueba sencilla:
import { IsExact } from 'conditional-type-checks';
type TestType = string;
type ExpectedType = string;
// Verificamos que los tipos coincidan exactamente
type TestPass = IsExact<TestType, ExpectedType>; // true
// Si los tipos no coinciden, se producirá un error de compilación
// type TestFail = IsExact<TestType, number>; // false
Para que la prueba realmente verifique algo, usa la construcción expectType o simplemente una asignación con verificación:
import { assert } from 'conditional-type-checks';
type MyType = { name: string; age: number };
// Verificamos que MyType sea un objeto con los campos name y age
type Check = assert<IsExact<MyType, { name: string; age: number }>>; // OK
Si la verificación falla, TypeScript mostrará un error de compilación con el mensaje Type 'false' does not satisfy the constraint 'true'.
Métodos y funciones principales
La librería ofrece varias utilidades clave. Veamos cada una en detalle.
1. IsExact<T, U>
Firma: type IsExact<T, U> = ...
Qué hace: Verifica si los tipos T y U son exactamente iguales. Devuelve true si los tipos son idénticos, de lo contrario false. Tiene en cuenta todos los matices de TypeScript, incluyendo readonly, optional y otros modificadores.
import { IsExact } from 'conditional-type-checks';
type A = { readonly x: number };
type B = { x: number };
type Test1 = IsExact<A, B>; // false — readonly es diferente
type Test2 = IsExact<string, string>; // true
type Test3 = IsExact<string | number, number | string>; // true — el orden no importa
type Test4 = IsExact<{ a: string }, { a: string; b?: number }>; // false
2. IsAny<T>
Firma: type IsAny<T> = ...
Qué hace: Verifica si el tipo T es any. Devuelve true si es any, de lo contrario false. Útil para detectar usos accidentales de any que puedan debilitar la seguridad de tipos.
import { IsAny } from 'conditional-type-checks';
type Test1 = IsAny<any>; // true
type Test2 = IsAny<string>; // false
type Test3 = IsAny<unknown>; // false
type Test4 = IsAny<never>; // false
3. IsNever<T>
Firma: type IsNever<T> = ...
Qué hace: Verifica si el tipo T es never. Devuelve true si es never, de lo contrario false. Especialmente útil para validar tipos condicionales que deberían resultar en never en ciertos casos.
import { IsNever } from 'conditional-type-checks';
type Test1 = IsNever<never>; // true
type Test2 = IsNever<string>; // false
type Test3 = IsNever<any>; // false
4. IsUnion<T>
Firma: type IsUnion<T> = ...
Qué hace: Verifica si el tipo T es una unión de tipos. Devuelve true si es una unión, de lo contrario false. Muy útil para asegurarse de que un tipo no se ha simplificado incorrectamente.
import { IsUnion } from 'conditional-type-checks';
type Test1 = IsUnion<string | number>; // true
type Test2 = IsUnion<string>; // false
type Test3 = IsUnion<never>; // false
type Test4 = IsUnion<any>; // false
5. Has<T, U>
Firma: type Has<T, U> = ...
Qué hace: Verifica si el tipo T tiene al menos las propiedades definidas en U. Es decir, si T es asignable a U. Devuelve true si se cumple, de lo contrario false.
import { Has } from 'conditional-type-checks';
type Person = { name: string; age: number; email: string };
type Test1 = Has<Person, { name: string }>; // true
type Test2 = Has<Person, { name: string; age: number }>; // true
type Test3 = Has<Person, { name: string; salary: number }>; // false — falta salary
type Test4 = Has<string, { length: number }>; // true — string tiene length
6. assert<T>
Firma: type assert<T extends true> = ...
Qué hace: Es una utilidad para forzar una comprobación en tiempo de compilación. Si el tipo pasado no es true, se produce un error de compilación. Se usa junto con las otras utilidades para crear aserciones.
import { assert, IsExact } from 'conditional-type-checks';
type MyType = { id: number; name: string };
// Esta línea compila sin errores
type Check1 = assert<IsExact<MyType, { id: number; name: string }>>;
// Esta línea produce un error de compilación
// type Check2 = assert<IsExact<MyType, { id: number }>>; // Error
Ejemplos prácticos
Validación de tipos condicionales
Supongamos que tienes un tipo condicional complejo y quieres asegurarte de que funciona correctamente:
import { assert, IsExact } from 'conditional-type-checks';
type IsString<T> = T extends string ? true : false;
type Test1 = assert<IsExact<IsString<'hello'>, true>>; // Pasa
type Test2 = assert<IsExact<IsString<42>, false>>; // Pasa
type Test3 = assert<IsExact<IsString<string>, true>>; // Pasa
Verificación de tipos en una API
Imagina que estás desarrollando una API y quieres garantizar que los tipos de las respuestas sean correctos:
import { assert, IsExact } from 'conditional-type-checks';
type ApiResponse<T> = {
data: T;
error: null;
} | {
data: null;
error: string;
};
type SuccessResponse = ApiResponse<{ user: string }>;
type ExpectedSuccess = { data: { user: string }; error: null };
type Test = assert<IsExact<SuccessResponse, ExpectedSuccess>>; // Pasa
Conclusión
La librería conditional-type-checks es una herramienta indispensable para cualquier desarrollador que trabaje con tipos complejos en TypeScript. Permite escribir pruebas de tipos estáticas que se ejecutan en tiempo de compilación, garantizando la corrección de los tipos sin afectar al rendimiento en runtime. Ya sea que estés refactorizando, documentando o desarrollando librerías, esta librería te ayudará a mantener la integridad de tu sistema de tipos.
Para más información, consulta la documentación oficial en GitHub.
También en la biblioteca
Security Crypto para Kotlin: guía completa con ejemplos
Análisis de HTML en C# (.NET): bibliotecas HtmlAgilityPack y AngleSharp
Limitación de tasa en C++: bibliotecas y herramientas para control de solicitudes
Nodemailer para JavaScript (Node.js): guía completa con ejemplos