类型测试
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 部分 查看可能的匹配器列表。
理解错误信息
如果你正在使用 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
。在大多数情况下,这会告诉你问题所在。极其复杂的类型当然需要更多的调试工作,并且可能需要进行一些实验。如果错误消息确实具有误导性,请提出问题。
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>();
如果你觉得使用 expectTypeOf
API 并排查错误很困难,你总是可以使用更简单的 assertType
API:
const answer = 42;
assertType<number>(answer);
// @ts-expect-error answer 不是字符串
assertType<string>(answer);
TIP
使用 @ts-expect-error
语法时,你可能需要确保没有拼写错误。你可以通过将类型文件包含在 test.include
配置选项中来做到这一点,这样 Vitest 实际上也会_运行_这些测试,并在遇到 ReferenceError
时失败。
这将通过,因为它期望一个错误,但“answer”这个词有拼写错误,因此这是一个误报错误:
// @ts-expect-error answer 不是字符串
assertType<string>(answr);
运行类型检查
要启用类型检查,只需在 package.json
中的 Vitest 命令中添加 --typecheck
标志:
{
"scripts": {
"test": "vitest --typecheck"
}
}
现在你可以运行类型检查了:
npm run test
yarn test
pnpm run test
bun test
Vitest 根据你的配置使用 tsc --noEmit
或 vue-tsc --noEmit
,因此你可以从你的构建流程中删除这些脚本。