Testes de Tipo
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 isso com a opção de configuração typecheck.include
.
Internamente, o Vitest invoca o tsc
ou o vue-tsc
, conforme sua configuração, e analisa os resultados. O Vitest também exibirá erros de tipo encontrados no seu código-fonte. Você pode desativar essa funcionalidade com a opção de configuração typecheck.ignoreSourceErrors
.
É importante notar que o Vitest não executa esses arquivos; eles são apenas analisados estaticamente pelo compilador. Isso significa que, se você utilizar um nome dinâmico, test.each
ou test.for
, o nome do teste não será avaliado e será exibido como está.
WARNING
Antes do Vitest 2.1, sua configuração typecheck.include
substituía o padrão include
, o que resultava na verificação de tipo dos seus testes de tempo de execução, em vez de sua execução.
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.
Flags da CLI, como --allowOnly
e -t
, também são suportadas para a verificação de tipo.
import { assertType, expectTypeOf } from 'vitest';
import { mount } from './mount.js';
test('os tipos funcionam corretamente', () => {
expectTypeOf(mount).toBeFunction();
expectTypeOf(mount).parameter(0).toMatchTypeOf<{ name: string }>();
// @ts-expect-error name é uma string
assertType(mount({ name: 42 }));
});
Qualquer erro de tipo gerado 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 matchers disponíveis 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
utilizam um tipo auxiliar especial para produzir mensagens de erro o mais úteis possível. No entanto, há um detalhe importante para compreendê-las. Como as asserções são escritas de forma fluida (fluent API), a falha deve ocorrer no tipo "esperado", e não no tipo "real" (expect<Actual>().toEqualTypeOf<Expected>()
). Isso pode tornar as mensagens de erro de tipo um pouco confusas; por isso, esta biblioteca gera um tipo MismatchInfo
para tentar esclarecer qual é 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 que especifica os tipos "esperado" e "real". Em vez de interpretar a frase Os tipos da propriedade 'a' são incompatíveis // O tipo 'string' não é atribuível ao tipo "Esperado: string, Real: number"
literalmente, basta observar o nome da propriedade ('a'
) e a mensagem: Esperado: string, Real: number
. Isso indicará o problema na maioria dos casos. Tipos extremamente complexos, naturalmente, exigirão mais esforço para depuração e podem requerer 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 resultar em um tipo não chamável quando o tipo Actual
em teste não corresponde. Por exemplo, a falha para uma asserção como expectTypeOf(1).toBeString()
será algo como:
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 Esta expressão não pode ser chamada
não é muito útil; o erro significativo está na próxima linha: O tipo 'ExpectString<number>' não possui assinaturas de chamada
. Isso significa, essencialmente, que você passou um número, mas afirmou que deveria ser uma string.
Se o TypeScript adicionasse suporte para tipos "throw", essas mensagens de erro poderiam ser significativamente melhoradas. Até então, elas exigirão um certo grau de interpretação para serem compreendidas.
Objetos "esperados" concretos vs. argumentos de tipo
As mensagens de erro para uma asserção como a seguinte:
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 argumento de tipo (typearg) para o estilo .toEqualTypeOf({a: ''})
. Esta biblioteca só pode marcá-lo como uma falha comparando-o com um tipo Mismatch
genérico. Portanto, sempre que possível, utilize um argumento de tipo em vez de um tipo concreto para .toEqualTypeOf
e toMatchTypeOf
. Se for mais conveniente comparar dois tipos concretos, você pode usar o 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 identificar erros, pode sempre usar a API assertType
, que é mais simples:
const answer = 42;
assertType<number>(answer);
// @ts-expect-error answer não é uma string
assertType<string>(answer);
TIP
Ao usar a sintaxe @ts-expect-error
, é bom garantir que você 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
. Assim, o Vitest também executará esses testes e falhará com ReferenceError
.
Isso passará porque espera um erro, mas a palavra "answer" contém um erro de digitação, resultando em um falso positivo:
// @ts-expect-error answer não é uma string
assertType<string>(answr); //
Executando a Verificação de Tipo
Para habilitar a verificação de tipo, basta adicionar a opção --typecheck
ao seu comando Vitest em package.json
:
{
"scripts": {
"test": "vitest --typecheck"
}
}
Agora você pode realizar a verificação de tipo:
npm run test
yarn test
pnpm run test
bun test
O Vitest utiliza o tsc --noEmit
ou o vue-tsc --noEmit
, dependendo da sua configuração. Assim, você pode remover esses scripts do seu fluxo de trabalho.