Passer au contenu

Types de Tests

Vitest vous permet d’écrire des tests pour vos types, en utilisant les syntaxes expectTypeOf ou assertType. Par défaut, tous les tests à l’intérieur des fichiers *.test-d.ts sont considérés comme des tests de type, mais vous pouvez le modifier avec l’option de configuration typecheck.include.

Sous le capot, Vitest appelle tsc ou vue-tsc, selon votre configuration, et analyse les résultats. Vitest affichera également les erreurs de type dans votre code source, si elles sont trouvées. Vous pouvez désactiver cela avec l’option de configuration typecheck.ignoreSourceErrors.

Gardez à l’esprit que Vitest ne compile ni n’exécute ces fichiers, ils sont uniquement analysés statiquement par le compilateur, et c’est pourquoi vous ne pouvez pas utiliser d’instructions dynamiques. Cela signifie que vous ne pouvez pas utiliser de noms de test dynamiques, ni les APIs test.each, test.runIf, test.skipIf, test.concurrent. Mais vous pouvez utiliser d’autres APIs, comme test, describe, .only, .skip et .todo.

L’utilisation d’options de ligne de commande, comme --allowOnly et -t, est également prise en charge pour la vérification des types.

import { assertType, expectTypeOf } from 'vitest'
import { mount } from './mount.js'
test('mes types fonctionnent correctement', () => {
expectTypeOf(mount).toBeFunction()
expectTypeOf(mount).parameter(0).toMatchTypeOf<{ name: string }>()
// @ts-expect-error name est une chaîne
assertType(mount({ name: 42 }))
})

Toute erreur de type déclenchée dans un fichier de test sera considérée comme une erreur de test, donc vous pouvez utiliser n’importe quel truc de type que vous voulez pour tester les types de votre projet.

Vous pouvez voir une liste des matchers possibles dans la section API.

Lecture des Erreurs

Si vous utilisez l’API expectTypeOf, référez-vous à la documentation d’expect-type sur 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. Mais il y a une petite nuance à comprendre. Étant donné que les assertions sont écrites “de manière fluide”, l’échec doit concerner le type “attendu”, pas le type “réel” (expect<Actual>().toEqualTypeOf<Expected>()). Cela signifie que les erreurs de type peuvent être un peu déroutantes - donc cette bibliothèque produit un type MismatchInfo pour essayer de rendre explicite quelle est l’attente. Par exemple :

expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>()

Est une assertion qui échouera, puisque {a: 1} a pour 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 rapportée est un message lisible par l’homme spécifiant à 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 - il suffit de regarder le nom de la propriété ('a') et le message : Expected: string, Actual: number. Cela vous dira ce qui ne va pas, dans la plupart des cas. Les types extrêmement complexes seront bien sûr plus difficiles à déboguer, et peuvent nécessiter un peu d’expérimentation. Veuillez signaler un problème si les messages d’erreur sont réellement trompeurs.

Les méthodes toBe... (comme toBeString, toBeNumber, toBeVoid, etc.) échouent en se résolvant à un type non appelable lorsque le type Actual en cours de test ne correspond pas. Par exemple, l’échec d’une assertion comme expectTypeOf(1).toBeString() ressemblera à quelque chose comme 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 déclaré qu’il devrait être une chaîne.

Si TypeScript ajoutait le support pour les “types throw”, ces messages d’erreur pourraient être considérablement améliorés. Jusqu’à ce que cela se produise, ils nécessiteront un certain effort pour être interprétés.

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 }>()

Ceci est dû au fait que le compilateur TypeScript doit inférer le type argument pour le style .toEqualTypeOf({a: ''}), et cette bibliothèque ne peut le marquer comme un échec qu’en le comparant à un type générique Mismatch. Donc, si possible, utilisez un type argument plutôt qu’un type concret pour .toEqualTypeOf et toMatchTypeOf. Si c’est beaucoup 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 vous trouvez difficile de travailler avec l’API expectTypeOf et de comprendre les erreurs, vous pouvez toujours utiliser l’API assertType plus simple :

const answer = 42
assertType<number>(answer)
// @ts-expect-error answer n'est pas une chaîne
assertType<string>(answer)

Exécuter la vérification de type

Pour activer la vérification de type, ajoutez simplement l’option --typecheck à votre commande Vitest dans package.json :

{
"scripts": {
"test": "vitest --typecheck"
}
}

Maintenant, vous pouvez exécuter la vérification de type :

Fenêtre de terminal
npm run test

Vitest utilise tsc --noEmit ou vue-tsc --noEmit, selon votre configuration, donc vous pouvez retirer ces scripts de votre pipeline.