TypeScript conditional-type-checks: guía completa con ejemplos

Entrenador de Python en Línea para Principiantes

Aprende Python fácilmente sin sobrecargarte de teoría. Resuelve tareas prácticas con verificación automática, recibe pistas y escribe código directamente en el navegador, sin instalar nada.

Start Course

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.

Recommendations