Tests de type
Exemple de projet
Vitest vous permet d'écrire des tests pour vos types, en utilisant les syntaxes expectTypeOf
ou assertType
. Par défaut, tous les fichiers *.test-d.ts
sont considérés comme des tests de type, mais vous pouvez modifier ce comportement avec l'option de configuration typecheck.include
.
En coulisses, Vitest appelle tsc
ou vue-tsc
, selon votre configuration, et analyse les résultats. Vitest affichera également les erreurs de type détectées dans votre code source. Vous pouvez désactiver cette fonctionnalité avec l'option de configuration typecheck.ignoreSourceErrors
.
Notez que Vitest n'exécute pas ces fichiers ; ils sont uniquement analysés statiquement par le compilateur. Cela signifie que si vous utilisez un nom dynamique, test.each
ou test.for
, le nom du test ne sera pas évalué et s'affichera tel quel.
WARNING
Avant Vitest 2.1, votre configuration typecheck.include
remplaçait le modèle include
, ce qui avait pour conséquence que vos tests d'exécution n'étaient pas réellement exécutés, mais seulement vérifiés au niveau du type.
Depuis Vitest 2.1, si vos configurations include
et typecheck.include
se chevauchent, Vitest signalera les tests de type et les tests d'exécution comme des entrées distinctes.
Les drapeaux CLI, tels que --allowOnly
et -t
, sont également pris en charge pour la vérification des types.
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 }));
});
Toute erreur de type survenant dans un fichier de test sera traitée comme une erreur de test. Vous pouvez donc utiliser n'importe quelle technique de typage pour tester les types de votre projet.
Vous pouvez consulter une liste des matchers possibles dans la section API.
Lecture des erreurs
Si vous utilisez l'API expectTypeOf
, veuillez vous référer à la documentation d'expect-type concernant ses messages d'erreur.
Lorsque les types ne correspondent pas, .toEqualTypeOf
et .toMatchTypeOf
utilisent un type d'aide spécial pour produire des messages d'erreur aussi exploitables que possible. Cependant, leur compréhension requiert une certaine nuance. Étant donné que les assertions sont écrites de manière "fluide", l'échec est censé se produire sur le type "attendu", et non sur le type "réel" (expect<Actual>().toEqualTypeOf<Expected>()
). Cela signifie que les erreurs de type peuvent être un peu confuses. Cette bibliothèque génère donc un type MismatchInfo
pour tenter de clarifier ce qui est attendu. Par exemple :
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();
Est une assertion qui échouera, car {a: 1}
a le type {a: number}
et non {a: string}
. Le message d'erreur dans ce cas ressemblera à ceci :
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}>()
Notez que la contrainte de type signalée est un message compréhensible qui spécifie à la fois les types "attendus" et "réels". Plutôt que de prendre la phrase Types of property 'a' are incompatible // Type 'string' is not assignable to type "Expected: string, Actual: number"
au pied de la lettre, concentrez-vous simplement sur le nom de la propriété ('a'
) et le message : Expected: string, Actual: number
. Cela vous indiquera ce qui ne va pas, dans la plupart des cas. Les types extrêmement complexes nécessiteront bien sûr plus d'efforts de débogage et pourront requérir quelques expérimentations. Veuillez ouvrir une issue si les messages d'erreur sont réellement trompeurs.
Les méthodes toBe...
(comme toBeString
, toBeNumber
, toBeVoid
, etc.) échouent en se résolvant en un type non appelable lorsque le type Actual
testé ne correspond pas. Par exemple, l'échec d'une assertion comme expectTypeOf(1).toBeString()
ressemblera à ceci :
test/test.ts:999:999 - error TS2349: This expression is not callable.
Type 'ExpectString<number>' has no call signatures.
999 expectTypeOf(1).toBeString()
~~~~~~~~~~
La partie This expression is not callable
n'est pas très utile. L'erreur significative est la ligne suivante : Type 'ExpectString<number>' has no call signatures
. Cela signifie essentiellement que vous avez passé un nombre mais que vous avez déclaré qu'il devait être une chaîne.
Si TypeScript ajoutait la prise en charge des "throw" types, ces messages d'erreur pourraient être considérablement améliorés. D'ici là, il faudra les examiner attentivement.
Objets "attendus" concrets vs typeargs
Les messages d'erreur pour une assertion comme celle-ci :
expectTypeOf({ a: 1 }).toEqualTypeOf({ a: '' });
Seront moins utiles que pour une assertion comme celle-ci :
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();
C'est parce que le compilateur TypeScript doit inférer le typearg pour le style .toEqualTypeOf({a: ''})
, et cette bibliothèque ne peut le signaler comme un échec qu'en le comparant à un type générique Mismatch
. Donc, dans la mesure du possible, utilisez un typearg plutôt qu'un type concret pour .toEqualTypeOf
et toMatchTypeOf
. S'il est plus pratique de comparer deux types concrets, vous pouvez utiliser typeof
:
const one = valueFromFunctionOne({ some: { complex: inputs } });
const two = valueFromFunctionTwo({ some: { other: inputs } });
expectTypeOf(one).toEqualTypeOf<typeof two>();
Si l'API expectTypeOf
et la compréhension de ses erreurs vous semblent difficiles, vous pouvez toujours recourir à l'API assertType
, qui est plus simple :
const answer = 42;
assertType<number>(answer);
// @ts-expect-error answer is not a string
assertType<string>(answer);
TIP
Lorsque vous utilisez la syntaxe @ts-expect-error
, vous voudrez peut-être vous assurer que vous n'avez pas fait de faute de frappe. Vous pouvez le faire en incluant vos fichiers de type dans l'option de configuration test.include
, de sorte que Vitest exécutera également ces tests et échouera avec une ReferenceError
.
Ce test passera, car une erreur est attendue, mais le mot "answer" contient une faute de frappe, ce qui en fait un faux positif :
// @ts-expect-error answer is not a string
assertType<string>(answr);
Exécuter la vérification des types
Pour activer la vérification des types, ajoutez simplement le drapeau --typecheck
à votre commande Vitest dans package.json
:
{
"scripts": {
"test": "vitest --typecheck"
}
}
Vous pouvez désormais lancer la vérification des types :
npm run test
yarn test
pnpm run test
bun test
Vitest utilise tsc --noEmit
ou vue-tsc --noEmit
, selon votre configuration. Vous pouvez donc supprimer ces scripts de votre pipeline.