Testování typů
Ukázkový projekt
Vitest umožňuje psát testy pro vaše typy pomocí syntaxe expectTypeOf
nebo assertType
. Ve výchozím nastavení jsou všechny soubory s příponou *.test-d.ts
považovány za typové testy, ale toto nastavení můžete změnit pomocí konfigurační možnosti typecheck.include
.
Na pozadí Vitest volá tsc
nebo vue-tsc
(v závislosti na vaší konfiguraci) a zpracovává jejich výstupy. Vitest také vypíše typové chyby nalezené ve vašem zdrojovém kódu, pokud nějaké existují. Toto chování můžete zakázat pomocí konfigurační možnosti typecheck.ignoreSourceErrors
.
Mějte na paměti, že Vitest tyto soubory nespouští; jsou pouze staticky analyzovány kompilátorem. To znamená, že pokud použijete dynamický název testu, například s test.each
nebo test.for
, název testu nebude vyhodnocen – zobrazí se tak, jak je napsán.
WARNING
Před verzí Vitest 2.1 vaše nastavení typecheck.include
přepsalo vzor include
, což znamenalo, že vaše runtime testy se ve skutečnosti nespustily; byly pouze typově zkontrolovány.
Od Vitest 2.1, pokud se vaše include
a typecheck.include
překrývají, Vitest nahlásí typové testy a runtime testy jako samostatné položky.
Příznaky CLI, jako --allowOnly
a -t
, jsou také podporovány pro kontrolu typů.
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 }));
});
Jakákoli typová chyba vyvolaná uvnitř testovacího souboru bude považována za chybu testu, takže můžete použít jakýkoli typový trik, který chcete k testování typů vašeho projektu.
Seznam možných matcherů naleznete v sekci API.
Čtení chyb
Pokud používáte API expectTypeOf
, podívejte se na dokumentaci expect-type o jeho chybových zprávách.
Když se typy neshodují, .toEqualTypeOf
a .toMatchTypeOf
používají speciální pomocný typ k vytváření co nejpoužitelnějších chybových zpráv. Jejich porozumění však vyžaduje jistou nuanci. Jelikož jsou aserce formulovány "plynule", selhání by mělo být na "očekávaném" typu, nikoli na "skutečném" typu (expect<Actual>().toEqualTypeOf<Expected>()
). To znamená, že typové chyby mohou být trochu matoucí – proto tato knihovna produkuje typ MismatchInfo
, aby se pokusila explicitně vyjádřit, co je očekávání. Například:
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();
Je aserce, která selže, protože {a: 1}
má typ {a: number}
a ne {a: string}
. Chybová zpráva v tomto případě bude znít nějak takto:
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}>()
Všimněte si, že nahlášené typové omezení je zpráva čitelná pro lidi, specifikující jak "očekávané", tak "skutečné" typy. Namísto doslovného chápání věty Types of property 'a' are incompatible // Type 'string' is not assignable to type "Expected: string, Actual: number"
– stačí se podívat na název vlastnosti ('a'
) a zprávu: Expected: string, Actual: number
. To vám ve většině případů řekne, co je špatně. Extrémně složité typy budou samozřejmě vyžadovat více úsilí k ladění a mohou vyžadovat experimentování. Prosím nahlaste problém, pokud jsou chybové zprávy skutečně zavádějící.
Metody toBe...
(jako toBeString
, toBeNumber
, toBeVoid
atd.) selhávají tím, že se rozliší na nevolatelný typ, když se testovaný typ Actual
neshoduje. Například selhání aserce jako expectTypeOf(1).toBeString()
bude vypadat nějak takto:
test/test.ts:999:999 - error TS2349: This expression is not callable.
Type 'ExpectString<number>' has no call signatures.
999 expectTypeOf(1).toBeString()
~~~~~~~~~~
Část This expression is not callable
není příliš užitečná – smysluplná chyba je další řádek, Type 'ExpectString<number> has no call signatures
. To v podstatě znamená, že jste předali číslo, ale tvrdili jste, že by to měl být řetězec.
Pokud by TypeScript přidal podporu pro "throw" typy, tyto chybové zprávy by se mohly výrazně zlepšit. Do té doby bude jejich pochopení vyžadovat jistou námahu.
Konkrétní "očekávané" objekty vs. typové argumenty
Chybové zprávy pro aserci jako toto:
expectTypeOf({ a: 1 }).toEqualTypeOf({ a: '' });
Budou méně užitečné než pro aserci jako je toto:
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();
Je to proto, že kompilátor TypeScriptu potřebuje odvodit typový argument pro styl .toEqualTypeOf({a: ''})
, a tato knihovna jej může označit jako selhání pouze porovnáním s generickým typem Mismatch
. Kde je to možné, použijte typový argument spíše než konkrétní typ pro .toEqualTypeOf
a toMatchTypeOf
. Pokud je mnohem pohodlnější porovnávat dva konkrétní typy, můžete použít typeof
:
const one = valueFromFunctionOne({ some: { complex: inputs } });
const two = valueFromFunctionTwo({ some: { other: inputs } });
expectTypeOf(one).toEqualTypeof<typeof two>();
Pokud se vám zdá práce s API expectTypeOf
a odhalování chyb obtížné, můžete vždy použít jednodušší API assertType
:
const answer = 42;
assertType<number>(answer);
// @ts-expect-error answer is not a string
assertType<string>(answer);
TIP
Při použití syntaxe @ts-expect-error
se možná budete chtít ujistit, že jste neudělali překlep. To můžete udělat tak, že zahrnete své typové soubory do konfigurační možnosti test.include
, takže Vitest tyto testy skutečně spustí a selže s ReferenceError
.
Toto projde, protože očekává chybu, ale slovo „answer“ má překlep, takže se jedná o falešně pozitivní výsledek:
// @ts-expect-error answer is not a string
assertType<string>(answr); //
Spuštění kontroly typů
Pro povolení kontroly typů stačí přidat příznak --typecheck
do vašeho příkazu Vitest v package.json
:
{
"scripts": {
"test": "vitest --typecheck"
}
}
Nyní můžete spustit kontrolu typů:
npm run test
yarn test
pnpm run test
bun test
Vitest používá tsc --noEmit
nebo vue-tsc --noEmit
, v závislosti na vaší konfiguraci, takže tyto skripty můžete ze svého pracovního postupu odstranit.