Test dei Tipi
Progetto di Esempio
Vitest consente di scrivere test sui tipi utilizzando le sintassi expectTypeOf o assertType. Per impostazione predefinita, tutti i test all'interno dei file *.test-d.ts sono considerati test dei tipi, ma è possibile modificarlo tramite l'opzione di configurazione typecheck.include.
Internamente, Vitest invoca tsc o vue-tsc, a seconda della configurazione, e analizza i risultati. Vitest mostrerà anche gli errori di tipo nel codice sorgente, se presenti. È possibile disabilitare questo comportamento con l'opzione di configurazione typecheck.ignoreSourceErrors.
È importante notare che Vitest non esegue né compila questi file; vengono solo analizzati staticamente dal compilatore. Pertanto, non è possibile utilizzare istruzioni dinamiche, come nomi di test dinamici, o le API test.each, test.runIf, test.skipIf, test.concurrent. Tuttavia, è possibile utilizzare altre API, come test, describe, .only, .skip e .todo.
È supportato anche l'uso dei flag CLI, come --allowOnly e -t, per il controllo dei tipi.
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 }));
});Qualsiasi errore di tipo rilevato all'interno di un file di test sarà considerato come un errore del test stesso. Questo permette di utilizzare qualsiasi tecnica di tipo per testare i tipi del progetto.
È possibile consultare un elenco dei possibili matcher nella sezione API.
Interpretazione degli Errori
Se si utilizza l'API expectTypeOf, si consiglia di consultare la documentazione di expect-type sui suoi messaggi di errore.
Quando i tipi non corrispondono, .toEqualTypeOf e .toMatchTypeOf utilizzano un tipo helper speciale per generare messaggi di errore il più chiari possibile. Tuttavia, è importante comprendere una piccola sfumatura. Poiché le asserzioni sono scritte in modo "fluente", l'errore dovrebbe riguardare il tipo "previsto", non il tipo "effettivo" (expect<Actual>().toEqualTypeOf<Expected>()). Questo può rendere gli errori di tipo un po' confusi. Per questo motivo, questa libreria genera un tipo MismatchInfo per rendere esplicita l'aspettativa. Ad esempio:
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();Questa asserzione fallirà, poiché {a: 1} ha tipo {a: number} e non {a: string}. Il messaggio di errore in questo caso sarà simile a questo:
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}>()Si noti che il vincolo di tipo riportato è un messaggio leggibile che specifica sia i tipi "previsti" che "effettivi". Invece di interpretare letteralmente la frase Types of property 'a' are incompatible // Type 'string' is not assignable to type "Expected: string, Actual: number", concentrati sul nome della proprietà ('a') e sul messaggio: Expected: string, Actual: number. Questo indicherà la causa dell'errore nella maggior parte dei casi. I tipi estremamente complessi richiederanno ovviamente più impegno per il debug e potrebbero richiedere una certa sperimentazione. Si prega di aprire un issue se i messaggi di errore risultano fuorvianti.
I metodi toBe... (come toBeString, toBeNumber, toBeVoid ecc.) falliscono risolvendo a un tipo non chiamabile quando il tipo Actual sotto test non corrisponde. Ad esempio, il fallimento per un'asserzione come expectTypeOf(1).toBeString() sarà simile a questo:
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 This expression is not callable non è molto utile; l'errore significativo è la riga successiva, Type 'ExpectString<number> has no call signatures. Questo indica essenzialmente che è stato passato un numero ma è stato asserito che dovesse essere una stringa.
Se TypeScript aggiungesse il supporto per i tipi "throw" questi messaggi di errore potrebbero essere notevolmente migliorati. Fino ad allora, richiederanno un'attenta analisi.
Oggetti "previsti" concreti vs typeargs
I messaggi di errore per un'asserzione come questa:
expectTypeOf({ a: 1 }).toEqualTypeOf({ a: '' });Saranno meno utili rispetto a un'asserzione come questa:
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();Questo perché il compilatore TypeScript deve inferire il typearg per lo stile .toEqualTypeOf({a: ''}), e questa libreria può solo segnalarlo come un fallimento confrontandolo con un tipo generico Mismatch. Quindi, ove possibile, usa un typearg invece di un tipo concreto per .toEqualTypeOf e toMatchTypeOf. Se è molto più conveniente confrontare due tipi concreti, puoi usare typeof:
const one = valueFromFunctionOne({ some: { complex: inputs } });
const two = valueFromFunctionTwo({ some: { other: inputs } });
expectTypeOf(one).toEqualTypeof<typeof two>();Se si riscontrano difficoltà nell'utilizzo dell'API expectTypeOf e nell'interpretazione degli errori, è sempre possibile utilizzare l'API assertType, che è più semplice:
const answer = 42;
assertType<number>(answer);
// @ts-expect-error answer is not a string
assertType<string>(answer);TIP
Quando si utilizza la sintassi @ts-expect-error, è consigliabile assicurarsi di non aver commesso un errore di battitura. È possibile farlo includendo i file di tipo nell'opzione di configurazione test.include. In questo modo, Vitest eseguirà effettivamente questi test e genererà un errore ReferenceError.
Questo test passerà, perché si aspetta un errore, ma la parola "answer" ha un errore di battitura, quindi è un falso positivo:
// @ts-expect-error answer is not a string
assertType<string>(answr); //Esecuzione del Typechecking
Dalla versione 1.0 di Vitest, per abilitare il typechecking, è sufficiente aggiungere il flag --typecheck al comando Vitest nel file package.json:
{
"scripts": {
"test": "vitest --typecheck"
}
}Ora è possibile eseguire il typecheck:
npm run testyarn testpnpm run testbun testVitest utilizza tsc --noEmit o vue-tsc --noEmit, a seconda della configurazione, quindi è possibile rimuovere questi script dal flusso di lavoro.