跳至内容

测试类型

示例项目

GitHub - 在线体验

Vitest 允许你使用 expectTypeOfassertType 语法为你的类型编写测试。默认情况下,*.test-d.ts 文件中的所有测试都被视为类型测试,但你可以使用 typecheck.include 配置选项来更改它。

在幕后,Vitest 会根据你的配置调用 tscvue-tsc,并解析结果。如果 Vitest 发现任何类型错误,它也会在你的源代码中打印出来。你可以使用 typecheck.ignoreSourceErrors 配置选项来禁用它。

请记住,Vitest 不会运行或编译这些文件,它们只是由编译器进行静态分析,因此你不能使用任何动态语句。这意味着你不能使用动态测试名称,以及 test.eachtest.runIftest.skipIftest.concurrent API。但你可以使用其他 API,例如 testdescribe.only.skip.todo

还支持使用 CLI 标志,例如 --allowOnly-t 进行类型检查。

ts
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 类型来尝试明确地说明期望是什么。例如

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 // Type 'string' is not assignable to type "Expected: string, Actual: number" 理解得太字面 - 只看属性名称 ('a') 和消息:Expected: string, Actual: number。在大多数情况下,这将告诉你哪里错了。当然,极其复杂的类型将需要更多调试工作,并且可能需要一些实验。如果错误消息实际上具有误导性,请 提交问题

toBe... 方法(如 toBeStringtoBeNumbertoBeVoid 等)在测试的 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 添加了对 “抛出”类型 的支持,这些错误消息可以得到显著改进。在此之前,它们将需要一定程度的眯眼观察。

具体“预期”对象与类型参数

对于类似这样的断言,错误消息

ts
expectTypeOf({ a: 1 }).toEqualTypeOf({ a: '' })

将不如类似这样的断言有用

ts
expectTypeOf({ a: 1 }).toEqualTypeOf<{ a: string }>()

这是因为 TypeScript 编译器需要推断 .toEqualTypeOf({a: ''}) 样式的类型参数,而这个库只能通过将其与泛型 Mismatch 类型进行比较来标记它为失败。因此,在可能的情况下,请使用类型参数而不是具体类型来进行 .toEqualTypeOftoMatchTypeOf。如果比较两个具体类型更方便,你可以使用 typeof

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

expectTypeOf(one).toEqualTypeof<typeof two>()

如果你发现使用 expectTypeOf API 和找出错误很困难,你始终可以使用更简单的 assertType API

ts
const answer = 42

assertType<number>(answer)
// @ts-expect-error answer is not a string
assertType<string>(answer)

提示

当使用 @ts-expect-error 语法时,你可能需要确保你没有打错字。你可以通过将你的类型文件包含在 test.include 配置选项中来做到这一点,这样 Vitest 也会实际运行这些测试,并使用 ReferenceError 失败。

这将通过,因为它期望出现错误,但单词“answer”有错别字,因此这是一个误报错误

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

运行类型检查

从 Vitest 1.0 开始,要启用类型检查,只需在 package.json 中的 Vitest 命令中添加 --typecheck 标志

json
{
  "scripts": {
    "test": "vitest --typecheck"
  }
}

现在你可以运行类型检查

bash
npm run test
bash
yarn test
bash
pnpm run test
bash
bun test

Vitest 使用 tsc --noEmitvue-tsc --noEmit(取决于你的配置),因此你可以从你的管道中删除这些脚本。