Для бібліотек, спільних утиліт і складних генеріків типи - частина публічного API. Регресія в типі (функція почала повертати any, перестала ловити неправильний аргумент) - такий самий баг, як помилка в логіці. Звичайні тести її не помітять.
1. @ts-expect-error - перевірка, що помилка БУДЕ:
// @ts-expect-error - id має бути числом
loadUser('42');
Якщо рядок перестане бути помилкою, компілятор скаже Unused '@ts-expect-error' directive (TS2578). Так тестують, що тип забороняє неправильне використання.
Не плутати з @ts-ignore: той мовчки приховує будь-яку помилку і не скаже, якщо її вже немає. У коді застосунку @ts-ignore - майже завжди погана ідея; @ts-expect-error з поясненням чесніший.
2. expect-type - перевірка, що тип ТОЧНО такий:
import { expectTypeOf } from 'expect-type';
const row = queryBuilder.select('id', 'name').build();
expectTypeOf(row).toEqualTypeOf<{ id: number; name: string }>();
expectTypeOf(parseAge).returns.toEqualTypeOf<Result<number, AgeError>>();
expectTypeOf(useCart).toBeFunction();
Перевірка відбувається під час компіляції (tsc --noEmit). У Vitest той самий API вбудований (expectTypeOf), а режим vitest --typecheck запускає файли *.test-d.ts як типові тести.
3. tsd - окремий інструмент для .d.ts бібліотек: перевіряє типи з погляду споживача пакета (expectType, expectError). Використовує вбудовану версію компілятора, тож результат не залежить від TypeScript у проєкті.
Що тестувати:
- виведення типів генеріків і перевантажень - результат має бути точним, а не
anyчиunknown; - заборонені виклики - неправильні аргументи мають давати помилку;
- звуження: після type guard тип звужено правильно;
- утиліти типів (
DeepPartial,PathOf<T>) - на граничних випадках:never, об'єднання, порожні об'єкти,any.
Пастки:
- перевірка «дорівнює» складніша, ніж здається:
anyсумісний з усім, тож наївна перевірка через присвоєння пропуститьany.toEqualTypeOfвраховує це; - тести типів запускаються лише в CI з
tsc- Vite і esbuild при збиранні типи не перевіряють. Без крокуtsc --noEmitу CI типові тести нічого не ловлять.