Tipos de prueba
Consejo
Vitest te permite escribir pruebas para tus tipos utilizando las sintaxis expectTypeOf
o assertType
. Por defecto, todos los archivos con la extensión *.test-d.ts
se consideran pruebas de tipo, pero puedes modificar esta configuración con la opción typecheck.include
.
Internamente, Vitest invoca a tsc
o vue-tsc
, según tu configuración, y analiza sus resultados. Vitest también mostrará los errores de tipo encontrados en tu código fuente. Puedes deshabilitar esta funcionalidad con la opción de configuración typecheck.ignoreSourceErrors
.
Es importante tener en cuenta que Vitest no ejecuta estos archivos; solo son analizados estáticamente por el compilador. Esto implica que si utilizas nombres dinámicos, test.each
o test.for
, el nombre de la prueba no se evaluará y se mostrará tal cual.
WARNING
Antes de Vitest 2.1, la opción typecheck.include
sobrescribía el patrón include
, lo que significaba que tus pruebas de tiempo de ejecución no se ejecutaban; solo se realizaba la verificación de tipos.
A partir de Vitest 2.1, si tus patrones include
y typecheck.include
se solapan, Vitest reportará las pruebas de tipo y las pruebas de tiempo de ejecución como entradas separadas.
El uso de banderas CLI, como --allowOnly
y -t
, también es compatible para la verificación de tipos.
import { assertType, expectTypeOf } from 'vitest';
import { mount } from './mount.js';
test('mis tipos funcionan adecuadamente', () => {
expectTypeOf(mount).toBeFunction();
expectTypeOf(mount).parameter(0).toMatchTypeOf<{ name: string }>();
// @ts-expect-error name es un string
assertType(mount({ name: 42 }));
});
Cualquier error de tipo que se active dentro de un archivo de prueba será tratado como un fallo de prueba. Esto te permite emplear cualquier técnica de tipado para validar los tipos de tu proyecto.
Puedes consultar una lista de posibles "matchers" (comparadores) en la sección de API.
Lectura de errores
Si estás utilizando la API expectTypeOf
, consulta la documentación de expect-type sobre sus mensajes de error.
Cuando los tipos no coinciden, .toEqualTypeOf
y .toMatchTypeOf
emplean un tipo auxiliar especial para generar mensajes de error lo más útiles posible. Sin embargo, su interpretación tiene un pequeño matiz. Dado que las aserciones se escriben de forma "fluida", el fallo debería residir en el tipo "esperado", no en el tipo "real" (expect<Actual>().toEqualTypeOf<Expected>()
). Esto puede hacer que los errores de tipo sean algo confusos, por lo que esta biblioteca produce un tipo MismatchInfo
para intentar hacer explícita la expectativa. Por ejemplo:
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();
Esta aserción fallará, ya que {a: 1}
tiene el tipo {a: number}
y no {a: string}
. El mensaje de error en este caso dirá algo como esto:
test/test.ts:999:999 - error TS2344: Type '{ a: string; }' does not satisfy the constraint '{ a: \\"Expected: string, Actual: number\\"; }'.
Types of property 'a' are incompatible.
Type 'string' is not assignable to type '\\"Expected: string, Actual: number\\"'.
999 expectTypeOf({a: 1}).toEqualTypeOf<{a: string}>()
Observa que la restricción de tipo reportada es un mensaje legible que especifica tanto los tipos "esperado" como "real". En lugar de tomar la frase Los tipos de la propiedad 'a' no son compatibles // El tipo 'string' no es asignable al tipo "Expected: string, Actual: number"
literalmente, simplemente fíjate en el nombre de la propiedad ('a'
) y el mensaje: Expected: string, Actual: number
. Esto te indicará el problema en la mayoría de los casos. Los tipos extremadamente complejos, por supuesto, requerirán más esfuerzo para depurar y pueden necesitar cierta experimentación. Por favor, abre una incidencia si los mensajes de error son realmente engañosos.
Los métodos toBe...
(como toBeString
, toBeNumber
, toBeVoid
, etc.) fallan al resolverse a un tipo no invocable cuando el tipo Actual
bajo prueba no coincide. Por ejemplo, el fallo de una aserción como expectTypeOf(1).toBeString()
se verá algo así:
test/test.ts:999:999 - error TS2349: This expression is not callable.
Type 'ExpectString<number>' has no call signatures.
999 expectTypeOf(1).toBeString()
~~~~~~~~~~
La parte Esta expresión no es invocable
no es de gran ayuda; el error significativo es la siguiente línea: El tipo 'ExpectString<number>' no tiene firmas de invocación
. Esto esencialmente significa que se proporcionó un número, pero se afirmó que debería ser un string.
Si TypeScript añadiera soporte para los "throw" types, estos mensajes de error podrían mejorarse significativamente. Hasta entonces, requerirán un cierto esfuerzo para interpretarlos.
Objetos "esperados" concretos vs. argumentos de tipo
Los mensajes de error en una aserción como esta:
expectTypeOf({ a: 1 }).toEqualTypeOf({ a: '' });
Resultarán menos útiles que para una aserción como esta:
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();
Esto se debe a que el compilador de TypeScript necesita inferir el argumento de tipo (typearg) para el estilo .toEqualTypeOf({a: ''})
, y esta biblioteca solo puede indicarlo como un fallo comparándolo con un tipo Mismatch
genérico. Por lo tanto, siempre que sea posible, utiliza un argumento de tipo (typearg) en lugar de un tipo concreto para .toEqualTypeOf
y toMatchTypeOf
. Si te resulta mucho más conveniente comparar dos tipos concretos, puedes usar typeof
:
const one = valueFromFunctionOne({ some: { complex: inputs } });
const two = valueFromFunctionTwo({ some: { other: inputs } });
expectTypeOf(one).toEqualTypeof<typeof two>();
Si te resulta difícil trabajar con la API expectTypeOf
y descifrar errores, siempre puedes usar la API assertType
, que es más sencilla:
const answer = 42;
assertType<number>(answer);
// @ts-expect-error answer no es un string
assertType<string>(answer);
TIP
Al usar la sintaxis @ts-expect-error
, quizás quieras asegurarte de no haber cometido un error tipográfico. Puedes lograrlo incluyendo tus archivos de tipo en la opción de configuración test.include
, para que Vitest también ejecute estas pruebas y genere un ReferenceError
.
Esto será aceptado, porque espera un error, pero la palabra "answer" tiene un error tipográfico, lo que resulta en un falso positivo:
// @ts-expect-error answer no es un string
assertType<string>(answr); //
Ejecutar verificación de tipos
Para habilitar la verificación de tipos, simplemente añade la bandera --typecheck
a tu comando Vitest en package.json
:
{
"scripts": {
"test": "vitest --typecheck"
}
}
Ahora puedes ejecutar la verificación de tipos:
npm run test
yarn test
pnpm run test
bun test
Vitest utiliza tsc --noEmit
o vue-tsc --noEmit
, dependiendo de tu configuración, por lo que puedes eliminar estos scripts de tu flujo de trabajo.