进阶 vitest.dev 2026-10-22 19:00:41 · 18 阅读

第10章 Vitest 类型测试指南:使用 expectTypeOf 与 assertType

测试类型 Sample ProjectGitHub - Play Online Vitest 允许你使用 `expectTypeOf` 或 `assertType` 语法来为类型编写测试。默认情况下,`*.test-d.ts` 文件中的所有测试都被视为类型测试,但你可以通过 `typecheck.include` 配置项进行修改。

在底层,Vitest 会根据你的配置调用 `tsc` 或 `vue-tsc`,并解析结果。如果检测到源代码中存在类型错误,Vitest 也会将其打印出来。你可以通过 `typecheck.ignoreSourceErrors` 配置项禁用此功能。

请注意,Vitest 不会运行这些文件,它们仅由编译器进行静态分析。这意味着,如果你使用动态名称或 `test.each`、`test.for`,测试名称不会被求值——它将按原样显示。

警告 在 Vitest 2.1 之前,`typecheck.include` 会覆盖 `include` 模式,导致运行时测试实际上并未运行;它们只进行了类型检查。

从 Vitest 2.1 开始,如果 `include` 和 `typecheck.include` 重叠,Vitest 会将类型测试和运行时测试作为独立条目报告。

类型检查也支持 `--allowOnly` 和 `-t` 等 CLI 标志。

`mount.test-d.ts` ```ts import { assertType, expectTypeOf } from 'vitest' import { mount } from './mount.js'

test('my types work properly', () => { expectTypeOf(mount).toBeFunction() expectTypeOf(mount).parameter(0).toExtend<{ name: string }>()

// @ts-expect-error name is a string assertType(mount({ name: 42 })) }) ``` 测试文件中触发的任何类型错误都会被视为测试错误,因此你可以使用任何类型技巧来测试项目的类型。

你可以在 API 部分查看可能的匹配器列表。

读取错误 如果你使用 `expectTypeOf` API,请参考 `expect-type` 文档中关于其错误消息的内容。

当类型不匹配时,`.toEqualTypeOf` 和 `.toExtend` 使用一种特殊的辅助类型来生成尽可能可操作(actionable)的错误消息。但理解它们有一些细微之处。由于断言是以“流畅式”(fluently)编写的,失败应体现在“期望”类型上,而不是“实际”类型(即 `expect().toEqualTypeOf()`)。这意味着类型错误可能会令人困惑——因此该库生成 `MismatchInfo` 类型,试图明确期望是什么。例如:

```ts 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”(属性 'a' 的类型不兼容)或“Type 'string' is not assignable to type 'Expected: string, Actual: number'”(类型 'string' 不能分配给类型 'Expected: string, Actual: number')——只需查看属性名('a')和消息:Expected: string, Actual: number(期望:string,实际:number)。在大多数情况下,这会告诉你问题所在。当然,极其复杂的类型需要更多的调试努力,可能需要一些实验。如果错误消息确实具有误导性,请提出 issue。

`toBe...` 方法(如 `toBeString`、`toBeNumber`、`toBeVoid` 等)在待测的实际类型不匹配时,会通过解析为一个不可调用的类型来判定失败。例如,像 `expectTypeOf(1).toBeString()` 这样的断言,其失败形式如下:

``` test/test.ts:999:999 - error TS2349: This expression is not callable. Type 'ExpectString' has no call signatures.

999 expectTypeOf(1).toBeString() ~~~~~~~~~~ ``` “This expression is not callable”(此表达式不可调用)这部分并不是很有帮助——有意义的错误在下一行:“Type 'ExpectString' has no call signatures”(类型 'ExpectString' 没有调用签名)。这基本上意味着你传入了一个 number,但断言它应该是 string。

如果 TypeScript 支持“throw”类型,这些错误消息可以显著改进。在那之前,它们需要稍加仔细查看才能理解。

具体的“期望”对象 vs 类型参数 像这样断言的错误消息:

```ts expectTypeOf({ a: 1 }).toEqualTypeOf({ a: '' }) ``` 将不如这种断言有帮助:

```ts expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>() ``` 这是因为 TypeScript 编译器需要推断 `.toEqualTypeOf({a: ''})` 这种风格中的类型参数,而该库只能通过将其与通用的 `Mismatch` 类型进行比较来标记为失败。因此,尽可能使用类型参数而不是具体类型来进行 `.toEqualTypeOf` 和 `.toExtend` 的比较。如果比较两个具体类型更方便,你可以使用 `typeof`:

```ts const one = valueFromFunctionOne({ some: { complex: inputs } }) const two = valueFromFunctionTwo({ some: { other: inputs } })

expectTypeOf(one).toEqualTypeOf() ``` 如果你发现使用 `expectTypeOf` API 和排查错误比较困难,你始终可以使用更简单的 `assertType` API:

```ts const answer = 42

assertType(answer) // @ts-expect-error answer is not a string assertType(answer) ``` 提示 使用 `@ts-expect-error` 语法时,你可能需要确保没有拼写错误。你可以通过将类型文件包含在 `test.include` 配置选项中来实现,这样 Vitest 也会实际运行这些测试,并在出现 `ReferenceError` 时失败。

这应该会通过,因为它期望一个错误,但单词 “answer” 有拼写错误,导致误报:

```ts // @ts-expect-error answer is not a string assertType(answr) ```

运行类型检查 要启用类型检查,只需在 `package.json` 中的 Vitest 命令添加 `--typecheck` 标志:

`package.json` ```json { "scripts": { "test": "vitest --typecheck" } } ``` 现在你可以运行类型检查: npm / yarn / pnpm / bun

```bash npm run test yarn test pnpm run test bun test ``` Vitest 根据你的配置使用 `tsc --noEmit` 或 `vue-tsc --noEmit`,因此你可以从管道中移除这些脚本。

评论 (0)