Testando Tipos
Projeto de Exemplo
O Vitest permite que você escreva testes para seus tipos, utilizando as sintaxes expectTypeOf
ou assertType
. Por padrão, todos os arquivos *.test-d.ts
são considerados testes de tipo, mas você pode ajustar essa configuração com a opção typecheck.include
.
Internamente, o Vitest invoca tsc
ou vue-tsc
, conforme sua configuração, e analisa os resultados. O Vitest também exibirá erros de tipo encontrados em seu código-fonte. Você pode desativar essa funcionalidade com a opção typecheck.ignoreSourceErrors
.
É importante notar que o Vitest não executa esses arquivos; eles são apenas analisados estaticamente pelo compilador. Isso implica que, se você utilizar um nome dinâmico, test.each
ou test.for
, o nome do teste não será avaliado – ele será exibido exatamente como está.
WARNING
Antes do Vitest 2.1, a opção typecheck.include
sobrescrevia o padrão include
, o que significava que seus testes de tempo de execução não eram de fato executados, apenas verificados quanto ao tipo.
A partir do Vitest 2.1, se suas configurações include
e typecheck.include
se sobrepõem, o Vitest reportará testes de tipo e testes de tempo de execução como entradas separadas.
O uso de opções da CLI, como --allowOnly
e -t
, também é suportado para a verificação de tipo.
import { assertType, expectTypeOf } from 'vitest';
import { mount } from './mount.js';
test('my types work properly', () => {
expectTypeOf(mount).toBeFunction();
expectTypeOf(mount).parameter(0).toMatchTypeOf<{ name: string }>();
// @ts-expect-error name is a string
assertType(mount({ name: 42 }));
});
Qualquer erro de tipo acionado dentro de um arquivo de teste será tratado como um erro de teste, permitindo que você utilize qualquer técnica de tipagem para testar os tipos do seu projeto.
Você pode consultar uma lista de possíveis matchers na seção da API.
Lendo Erros
Se você estiver utilizando a API expectTypeOf
, consulte a documentação do expect-type sobre suas mensagens de erro.
Quando os tipos não correspondem, .toEqualTypeOf
e .toMatchTypeOf
empregam um tipo auxiliar especial para gerar mensagens de erro o mais claras possível. No entanto, há uma pequena sutileza em sua interpretação. Como as asserções são escritas de forma "fluente", a falha deve estar no tipo "esperado", e não no tipo "real" (expect<Actual>().toEqualTypeOf<Expected>()
). Isso pode tornar os erros de tipo um pouco confusos – por isso, esta biblioteca produz um tipo MismatchInfo
para tentar explicitar a expectativa. Por exemplo:
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();
Esta é uma asserção que falhará, pois {a: 1}
tem o tipo {a: number}
e não {a: string}
. A mensagem de erro neste caso será algo como:
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}>()
Observe que a restrição de tipo relatada é uma mensagem legível por humanos, especificando os tipos "esperado" e "real". Em vez de interpretar literalmente a frase Types of property 'a' are incompatible // Type 'string' is not assignable to type "Expected: string, Actual: number"
, basta focar no nome da propriedade ('a'
) e na mensagem: Expected: string, Actual: number
. Isso indicará o que está errado na maioria dos casos. Tipos extremamente complexos, é claro, exigirão mais esforço para depurar e podem demandar alguma experimentação. Por favor, abra uma issue se as mensagens de erro forem realmente enganosas.
Os métodos toBe...
(como toBeString
, toBeNumber
, toBeVoid
etc.) falham ao resolver para um tipo não chamável quando o tipo Actual
sob teste não corresponde. Por exemplo, a falha para uma asserção como expectTypeOf(1).toBeString()
será algo assim:
test/test.ts:999:999 - error TS2349: This expression is not callable.
Type 'ExpectString<number>' has no call signatures.
999 expectTypeOf(1).toBeString()
~~~~~~~~~~
A parte This expression is not callable
não é muito útil – o erro significativo é a próxima linha: Type 'ExpectString<number>' has no call signatures
. Isso basicamente significa que você passou um número, mas afirmou que deveria ser uma string.
Se o TypeScript adicionasse suporte para "tipos de lançamento" (throw types), essas mensagens de erro poderiam ser significativamente aprimoradas. Até lá, será necessário um certo esforço para compreendê-las.
Objetos "esperados" concretos vs. typeargs
Mensagens de erro para uma asserção como esta:
expectTypeOf({ a: 1 }).toEqualTypeOf({ a: '' });
Serão menos úteis do que para uma asserção como esta:
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();
Isso ocorre porque o compilador TypeScript precisa inferir o typearg para o estilo .toEqualTypeOf({a: ''})
, e esta biblioteca só pode marcá-lo como uma falha comparando-o com um tipo Mismatch
genérico. Portanto, sempre que possível, utilize um typearg em vez de um tipo concreto para .toEqualTypeOf
e toMatchTypeOf
. Se for muito mais conveniente comparar dois tipos concretos, você pode usar typeof
:
const one = valueFromFunctionOne({ some: { complex: inputs } });
const two = valueFromFunctionTwo({ some: { other: inputs } });
expectTypeOf(one).toEqualTypeOf<typeof two>();
Se você achar difícil trabalhar com a API expectTypeOf
e depurar erros, você sempre pode usar a API assertType
mais simples:
const answer = 42;
assertType<number>(answer);
// @ts-expect-error answer is not a string
assertType<string>(answer);
TIP
Ao usar a sintaxe @ts-expect-error
, você pode querer ter certeza de que não cometeu um erro de digitação. Você pode fazer isso incluindo seus arquivos de tipo na opção de configuração test.include
, para que o Vitest também execute esses testes e falhe com ReferenceError
.
Isso passará, porque espera um erro, mas a palavra "answer" tem um erro de digitação, resultando em um falso positivo:
// @ts-expect-error answer is not a string
assertType<string>(answr);
Executar Verificação de Tipo
Para habilitar a verificação de tipo, basta adicionar a flag --typecheck
ao seu comando Vitest em package.json
:
{
"scripts": {
"test": "vitest --typecheck"
}
}
Agora você pode executar a verificação de tipo:
npm run test
yarn test
pnpm run test
bun test
O Vitest utiliza tsc --noEmit
ou vue-tsc --noEmit
, dependendo da sua configuração, então você pode remover esses scripts do seu pipeline.