Тестирование типов
Пример проекта
Vitest позволяет писать тесты для ваших типов, используя синтаксис expectTypeOf
или assertType
. По умолчанию, все файлы *.test-d.ts
считаются файлами с тестами типов, но это можно изменить с помощью опции конфигурации typecheck.include
.
Под капотом Vitest вызывает tsc
или vue-tsc
, в зависимости от вашей конфигурации, и анализирует результаты. Vitest также отображает ошибки типов в вашем исходном коде, если они обнаружены. Это поведение можно отключить с помощью опции конфигурации typecheck.ignoreSourceErrors
.
Важно понимать, что Vitest не запускает и не компилирует эти файлы. Они лишь статически анализируются компилятором. Следовательно, использование динамических операторов невозможно. Это означает, что динамические имена тестов и API test.each
, test.runIf
, test.skipIf
, test.concurrent
не поддерживаются. Однако, другие API, такие как test
, describe
, .only
, .skip
и .todo
, можно использовать.
Использование флагов 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
. Это подскажет вам, в чем проблема, в большинстве случаев. Чрезвычайно сложные типы, конечно, потребуют больше времени на отладку и могут потребовать некоторой экспериментальной работы. Пожалуйста, создайте issue, если сообщения об ошибках действительно вводят в заблуждение.
Методы 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" types, эти сообщения об ошибках можно было бы значительно улучшить. До тех пор для их интерпретации потребуется определенное усилие.
Конкретные объекты и аргументы типов
Сообщения об ошибках для утверждения, подобного этому:
expectTypeOf({ a: 1 }).toEqualTypeOf({ a: '' });
Будут менее полезными, чем для утверждения, подобного этому:
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>();
Это связано с тем, что компилятору TypeScript необходимо вывести аргумент типа (typearg) для .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); //
Запуск проверки типов
Начиная с Vitest 1.0, чтобы включить проверку типов, добавьте флаг --typecheck
в команду Vitest в файле package.json
:
{
"scripts": {
"test": "vitest --typecheck"
}
}
Теперь вы можете запустить проверку типов:
npm run test
yarn test
pnpm run test
bun test
Vitest использует tsc --noEmit
или vue-tsc --noEmit
, в зависимости от вашей конфигурации, поэтому эти скрипты можно удалить из процесса сборки.