Testy typów
Przykładowy projekt
Vitest umożliwia pisanie testów dla Twoich typów, wykorzystując składnię expectTypeOf
lub assertType
. Domyślnie wszystkie pliki z rozszerzeniem *.test-d.ts
są traktowane jako testy typów, ale możesz to zmienić za pomocą opcji konfiguracyjnej typecheck.include
.
Wewnętrznie Vitest wywołuje tsc
lub vue-tsc
, w zależności od konfiguracji, i analizuje ich wyniki. Vitest wyświetli również błędy typów znalezione w Twoim kodzie źródłowym. Możesz to wyłączyć za pomocą opcji konfiguracyjnej typecheck.ignoreSourceErrors
.
Pamiętaj, że Vitest nie uruchamia tych plików; są one jedynie statycznie analizowane przez kompilator. Oznacza to, że jeśli użyjesz dynamicznej nazwy testu, test.each
lub test.for
, nazwa testu nie zostanie oceniona – zostanie wyświetlona w niezmienionej formie.
WARNING
Przed Vitest 2.1, Twoja opcja typecheck.include
nadpisywała wzorzec include
, co oznaczało, że Twoje testy wykonawcze faktycznie się nie uruchamiały; były tylko sprawdzane pod kątem typów.
Od Vitest 2.1, jeśli Twoje opcje include
i typecheck.include
nakładają się, Vitest zgłosi testy typów i testy wykonawcze jako osobne pozycje.
Używanie flag CLI, takich jak --allowOnly
i -t
, jest również wspierane podczas sprawdzania typów.
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 }));
});
Każdy błąd typu wykryty w pliku testowym będzie traktowany jako błąd testu, co pozwala na użycie dowolnej techniki typowania do testowania typów Twojego projektu.
Listę dostępnych matcherów znajdziesz w sekcji API.
Interpretacja błędów
Jeśli używasz API expectTypeOf
, zapoznaj się z dokumentacją expect-type dotyczącą komunikatów o błędach.
Gdy typy się nie zgadzają, .toEqualTypeOf
i .toMatchTypeOf
używają specjalnego typu pomocniczego do generowania jak najbardziej pomocnych komunikatów o błędach. Jednak ich zrozumienie wymaga pewnego niuansu. Ponieważ asercje są pisane w sposób "płynny" (fluent API), błąd powinien dotyczyć typu "oczekiwanego", a nie typu "rzeczywistego" (expect<Actual>().toEqualTypeOf<Expected>()
). Oznacza to, że błędy typów mogą być nieco mylące – dlatego ta biblioteka generuje typ MismatchInfo
, aby wyraźnie określić, jakie jest oczekiwanie. Na przykład:
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();
Jest to asercja, która zakończy się niepowodzeniem, ponieważ {a: 1}
ma typ {a: number}
, a nie {a: string}
. Komunikat o błędzie w tym przypadku będzie wyglądał mniej więcej tak:
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}>()
Zauważ, że zgłoszone ograniczenie typu to zrozumiały dla ludzi komunikat określający zarówno typy "oczekiwane", jak i "rzeczywiste". Zamiast dosłownie traktować zdanie Types of property 'a' are incompatible // Type 'string' is not assignable to type "Expected: string, Actual: number"
, wystarczy spojrzeć na nazwę właściwości ('a'
) i komunikat: Expected: string, Actual: number
. W większości przypadków to powie Ci, co jest nie tak. Niezwykle złożone typy będą oczywiście wymagały większego wysiłku w debugowaniu i mogą wymagać eksperymentów. Proszę zgłoś problem, jeśli komunikaty o błędach są faktycznie mylące.
Metody typu toBe...
(takie jak toBeString
, toBeNumber
, toBeVoid
itp.) kończą się niepowodzeniem, zwracając typ nie-wywoływalny, gdy testowany typ Actual
nie pasuje. Na przykład, błąd dla asercji takiej jak expectTypeOf(1).toBeString()
będzie wyglądał mniej więcej tak:
test/test.ts:999:999 - error TS2349: This expression is not callable.
Type 'ExpectString<number>' has no call signatures.
999 expectTypeOf(1).toBeString()
~~~~~~~~~~
Część This expression is not callable
nie jest zbyt pomocna – istotny błąd to następna linia, Type 'ExpectString<number> has no call signatures
. Oznacza to zasadniczo, że przekazałeś liczbę, ale stwierdziłeś, że powinna być ciągiem znaków.
Gdyby TypeScript dodał obsługę "throw" types, te komunikaty o błędach mogłyby zostać znacznie ulepszone. Do tego czasu będą wymagały pewnej dozy wysiłku w ich interpretacji.
Konkretne obiekty "oczekiwane" vs. parametry typów
Komunikaty o błędach dla asercji takiej jak:
expectTypeOf({ a: 1 }).toEqualTypeOf({ a: '' });
Będą mniej pomocne niż dla asercji takiej jak:
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();
Dzieje się tak, ponieważ kompilator TypeScript musi wywnioskować parametr typu dla stylu .toEqualTypeOf({a: ''})
, a ta biblioteka może oznaczyć to jako błąd jedynie poprzez porównanie go z ogólnym typem Mismatch
. Dlatego, jeśli to możliwe, używaj parametru typu zamiast konkretnego typu dla .toEqualTypeOf
i toMatchTypeOf
. Jeśli znacznie wygodniej jest porównać dwa konkretne typy, możesz użyć operatora typeof
:
const one = valueFromFunctionOne({ some: { complex: inputs } });
const two = valueFromFunctionTwo({ some: { other: inputs } });
expectTypeOf(one).toEqualTypeof<typeof two>();
Jeśli masz trudności z pracą z API expectTypeOf
i interpretacją błędów, zawsze możesz skorzystać z prostszego API assertType
:
const answer = 42;
assertType<number>(answer);
// @ts-expect-error answer is not a string
assertType<string>(answer);
TIP
Używając składni @ts-expect-error
, powinieneś upewnić się, że nie popełniłeś literówki. Możesz to zrobić, włączając swoje pliki typów do opcji konfiguracyjnej test.include
, dzięki czemu Vitest faktycznie uruchomi te testy i zakończy się błędem ReferenceError
.
Test zostanie zaliczony, ponieważ oczekuje błędu, ale słowo „answer” zawiera literówkę, co prowadzi do fałszywie pozytywnego błędu:
// @ts-expect-error answer is not a string
assertType<string>(answr); //
Uruchom sprawdzanie typów
Aby włączyć sprawdzanie typów, po prostu dodaj flagę --typecheck
do swojego polecenia Vitest w package.json
:
{
"scripts": {
"test": "vitest --typecheck"
}
}
Teraz możesz uruchomić sprawdzanie typów:
npm run test
yarn test
pnpm run test
bun test
Vitest używa tsc --noEmit
lub vue-tsc --noEmit
, w zależności od konfiguracji, więc możesz usunąć te skrypty ze swojego potoku.