Тестирование типов
Пример проекта
Vitest позволяет писать тесты для ваших типов, используя синтаксис expectTypeOf
или assertType
. По умолчанию все файлы с расширением *.test-d.ts
рассматриваются как тесты типов, но вы можете изменить это поведение с помощью опции конфигурации typecheck.include
.
В основе своей Vitest вызывает tsc
или vue-tsc
, в зависимости от вашей конфигурации, и анализирует результаты их работы. Vitest также выводит ошибки типов, обнаруженные в вашем исходном коде, если таковые имеются. Вы можете отключить это с помощью опции конфигурации typecheck.ignoreSourceErrors
.
Имейте в виду, что Vitest не выполняет эти файлы, а только проводит их статический анализ с помощью компилятора. Это означает, что если вы используете динамическое имя, test.each
или test.for
, имя теста не будет определено и будет отображаться в исходном виде.
WARNING
До Vitest 2.1 ваш typecheck.include
переопределял параметр include
, что приводило к тому, что ваши тесты времени выполнения фактически не запускались, а только проверялись на типы.
Начиная с Vitest 2.1, если ваши include
и typecheck.include
пересекаются, Vitest будет сообщать о тестах типов и тестах времени выполнения как об отдельных записях.
Использование флагов CLI, таких как --allowOnly
и -t
, также поддерживается для проверки типов.
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 }));
});
Любая ошибка типа, возникшая внутри тестового файла, будет рассматриваться как ошибка теста. Таким образом, вы можете использовать любые трюки с типами для тестирования типов вашего проекта.
Список возможных методов сравнения можно найти в разделе API.
Чтение ошибок
Если вы используете API expectTypeOf
, обратитесь к документации expect-type по сообщениям об ошибках.
Когда типы не совпадают, .toEqualTypeOf
и .toMatchTypeOf
используют специальный вспомогательный тип для создания максимально полезных сообщений об ошибках. Однако есть небольшой нюанс в их понимании. Поскольку утверждения написаны в "цепочечном" стиле, ошибка должна быть в "ожидаемом" типе, а не в "фактическом" типе (expect<Actual>().toEqualTypeOf<Expected>()
). Это означает, что ошибки типов могут быть немного запутанными — поэтому эта библиотека создает тип MismatchInfo
, чтобы явно указать, что ожидается. Например:
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();
Это утверждение, которое приведет к ошибке, так как {a: 1}
имеет тип {a: number}
, а не {a: string}
. Сообщение об ошибке в этом случае будет выглядеть примерно так:
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}>()
Обратите внимание, что сообщаемое ограничение типа представляет собой удобочитаемое сообщение, явно указывающее как "ожидаемый", так и "фактический" типы. Вместо того чтобы буквально воспринимать предложение Types of property 'a' are incompatible // Type 'string' is not assignable to type "Expected: string, Actual: number"
, просто посмотрите на имя свойства ('a'
) и сообщение: Expected: string, Actual: number
. Это в большинстве случаев укажет на проблему. Чрезвычайно сложные типы, конечно, потребуют больше усилий для отладки и могут потребовать некоторых экспериментов. Пожалуйста, создайте проблему, если сообщения об ошибках на самом деле вводят в заблуждение.
Методы toBe...
(такие как toBeString
, toBeNumber
, toBeVoid
и т. д.) завершаются ошибкой, если тестируемый тип Actual
не совпадает, приводя к невызываемому типу. Например, ошибка для утверждения типа expectTypeOf(1).toBeString()
будет выглядеть примерно так:
test/test.ts:999:999 - error TS2349: This expression is not callable.
Type 'ExpectString<number>' has no call signatures.
999 expectTypeOf(1).toBeString()
~~~~~~~~~~
Часть This expression is not callable
не очень полезна — более значимая ошибка находится в следующей строке: Type 'ExpectString<number>' has no call signatures
. Это по сути означает, что вы передали число, но утверждали, что оно должно быть строкой.
Если бы TypeScript добавил поддержку "throw" типов, эти сообщения об ошибках могли бы быть значительно улучшены. До тех пор их понимание будет требовать некоторого внимания.
Конкретные "ожидаемые" объекты против аргументов типа
Сообщения об ошибках для такого утверждения:
expectTypeOf({ a: 1 }).toEqualTypeOf({ a: '' });
Будут менее полезными, чем сообщения для такого утверждения:
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();
Это связано с тем, что компилятору TypeScript необходимо вывести аргумент типа для стиля .toEqualTypeOf({a: ''})
, и эта библиотека может пометить его как ошибку только путем сравнения с общим типом Mismatch
. Поэтому, по возможности, используйте аргумент типа, а не конкретный тип для .toEqualTypeOf
и toMatchTypeOf
. Если гораздо удобнее сравнивать два конкретных типа, вы можете использовать typeof
:
const one = valueFromFunctionOne({ some: { complex: inputs } });
const two = valueFromFunctionTwo({ some: { other: inputs } });
expectTypeOf(one).toEqualTypeOf<typeof two>();
Если вам трудно работать с API expectTypeOf
и разбираться в ошибках, вы всегда можете использовать более простой API assertType
:
const answer = 42;
assertType<number>(answer);
// @ts-expect-error answer is not a string
assertType<string>(answer);
TIP
При использовании синтаксиса @ts-expect-error
вы можете захотеть убедиться, что не допустили опечатки. Вы можете сделать это, включив файлы ваших типов в опцию конфигурации test.include
, чтобы Vitest также фактически запускал эти тесты и выдавал ошибку ReferenceError
.
Этот тест пройдет, потому что ожидается ошибка, но в слове “answer” есть опечатка, что приводит к ложному срабатыванию:
// @ts-expect-error answer is not a string
assertType<string>(answr);
Запуск проверки типов
Чтобы включить проверку типов, просто добавьте флаг --typecheck
к вашей команде Vitest в package.json
:
{
"scripts": {
"test": "vitest --typecheck"
}
}
Теперь вы можете запустить проверку типов:
npm run test
yarn test
pnpm run test
bun test
Vitest использует tsc --noEmit
или vue-tsc --noEmit
, в зависимости от вашей конфигурации, поэтому вы можете удалить эти скрипты из вашего процесса сборки.