Тестирование типов
Пример проекта
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
, в зависимости от вашей конфигурации, поэтому вы можете удалить эти скрипты из вашего пайплайна.