类型测试
Vitest 允许你使用 expectTypeOf
或 assertType
语法来编写类型测试。默认情况下,所有 *.test-d.ts
文件中的测试都会被认为是类型测试。你可以使用 typecheck.include
配置选项来更改此设置。
Vitest 在底层会调用 tsc
或 vue-tsc
,具体取决于你的配置,并解析其输出结果。如果 Vitest 检测到类型错误,它会将错误信息打印在你的源代码中。你可以使用 typecheck.ignoreSourceErrors
配置选项来禁用此功能。
请注意,Vitest 不会实际运行或编译这些文件,它们仅由编译器进行静态分析,因此你不能使用任何动态语句。这意味着你不能使用动态测试名称,以及 test.each
、test.runIf
、test.skipIf
和 test.concurrent
等 API。但是你可以使用其他 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 部分 中查看可能的匹配器列表。
读取错误信息
如果你正在使用 expectTypeOf
API,请参阅 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。
当被测的 Actual
类型与期望不匹配时,toBe...
方法(例如 toBeString
、toBeNumber
、toBeVoid
等)会因为解析为不可调用的类型而失败。例如,像 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 的支持,这些错误消息可以得到显著改善。在此之前,理解这些错误信息需要费一番心思。
具体的“期望”对象 vs 类型参数
对于以下这种断言,错误信息的帮助性会较差:
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>();
如果你觉得使用 expectTypeOf API 比较困难,或者难以理解错误信息,可以考虑使用更简单的 assertType API:
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 开始,要启用类型检查,只需在 package.json
文件的 Vitest 命令中添加 --typecheck
标志:
{
"scripts": {
"test": "vitest --typecheck"
}
}
现在可以运行类型检查。
npm run test
yarn test
pnpm run test
bun test
Vitest 会根据你的配置使用 tsc --noEmit
或 vue-tsc --noEmit
,因此你可以从构建流程中移除这些命令。