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 test
yarn test
pnpm run test
bun test
Vitest utilizza tsc --noEmit
o vue-tsc --noEmit
, a seconda della configurazione, quindi è possibile rimuovere questi script dal flusso di lavoro.