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

第9章 扩展 Vitest Matchers

扩展断言(Matchers) 由于 Vitest 同时兼容 Chai 和 Jest,你可以使用 chai.use API,也可以使用 expect.extend,随你偏好。

本指南将探讨如何通过 expect.extend 来扩展断言。如果你对 Chai 的 API 感兴趣,可以去查看他们的指南。

要扩展默认断言,调用 expect.extend 并传入一个包含你自定义断言的对象。 ts expect.extend({ toBeFoo(received) { const { isNot } = this return { // 不要根据 isNot 修改 "pass",Vitest 会自动处理 pass: received === 'foo', message: () => `${received} is${isNot ? ' not' : ''} foo` } } }) 如果你使用 TypeScript,可以在全局声明文件(例如:vitest.d.ts)中通过以下代码扩展默认的 Matchers 接口: ts import 'vitest'

declare module 'vitest' { interface Matchers { toBeFoo: () => R } } 其中 R 是断言的返回类型,T 是接收值(received value)的类型。

对于同步执行的断言,返回类型应为 R。这意味着普通断言的返回类型是 void,而当断言与 .resolves、.rejects、expect.poll 或 expect.element 配合使用时,返回类型则是 Promise。如果期望参数(expected argument)应该与接收值具有相同的类型时,可以使用 T: ts declare module 'vitest' { interface Matchers { toEqualTyped: (expected: T) => R } } 提示:导入 vitest 会让 TypeScript 认为这是一个 ES 模块文件,如果不导入,类型声明将无法正常工作。

扩展 Matchers 接口会同时为 expect.extend、expect().* 和 expect.* 方法添加类型。 警告:别忘了在 tsconfig.json 中包含该全局声明文件。

断言的返回值应与以下类型兼容: ts interface SyncMatcherResult { pass: boolean message: () => string // 如果你传入了这些字段,当断言失败时,它们会自动出现在 diff 中, // 因此你不需要手动打印 diff actual?: unknown expected?: unknown meta?: object }

type MatcherResult = SyncMatcherResult | Promise 警告:如果断言实现是异步的,请将其返回类型声明为 Promise 而不是 R,并别忘了在测试中 await 它: ts expect.extend({ async toBeAsyncAssertion(received) { return { pass: received === 'foo', message: () => `expected ${received} to be foo`, } } })

declare module 'vitest' { interface Matchers { toBeAsyncAssertion: () => Promise } }

await expect('foo').toBeAsyncAssertion()

断言函数的第一个参数是接收值(即 expect(received) 中的那个值)。其余参数是直接传递给断言的。从 4.1 版本开始,Vitest 公开了几个可供自定义断言使用的类型: ts import type { // 函数类型 Matcher, // 返回值 MatcherResult, // 作为 this 可用的状态 MatcherState, } from 'vitest' import { expect } from 'vitest'

// 简单的断言,使用 "function" 以访问 "this" const customMatcher: Matcher = function (received) { // ... }

// 带参数的断言 const customMatcher: Matcher = function (received, arg1, arg2) { // ... }

// 带自定义注释的断言 function customMatcher(this: MatcherState, received: unknown, arg1: unknown, arg2: unknown): MatcherResult { // ... return { pass: false, message: () => 'something went wrong!', } }

expect.extend({ customMatcher }) 提示:要构建自定义快照断言(toMatchSnapshot() / toMatchInlineSnapshot() / toMatchFileSnapshot() 的包装器),请使用从 vitest 导出的 Snapshots。参见 自定义快照断言(Custom Snapshot Matchers)。

断言函数可以通过 this 上下文访问以下属性: isNot 如果断言在 not 上被调用(例如 expect(received).not.toBeFoo()),则返回 true。你不需要手动处理它,Vitest 会自动反转 pass 的值。 promise 如果断言在 resolved/rejected 上被调用,此值将包含修饰符的名称。否则,它将是空字符串。 equals 这是一个允许比较两个值的实用函数。如果值相等返回 true,否则返回 false。几乎所有断言内部都使用此函数。它默认支持带有非对称断言(asymmetric matchers)的对象。 customTesters 传递给 equals 的相等性测试器(equality testers),包括通过 expect.addEqualityTesters 注册的那些。

仅使用 equals 不会比较 Map 和 Set 的内容。要获得与 toEqual 相同的相等性,请添加 this.utils.iterableEquality: ts expect.extend({ toMyEqual(received: unknown, expected: unknown) { const pass = this.equals(received, expected, [...this.customTesters, this.utils.iterableEquality]) return { pass, message: () => `expected ${this.utils.printReceived(received)} to equal ${this.utils.printExpected(expected)}`, } }, }) utils 包含一组可用于显示消息的实用函数。

this 上下文还包含当前测试的信息。你也可以通过调用 expect.getState() 获取这些信息。最实用的属性有: currentTestName 当前测试的完整名称(包括 describe 块)。 task advanced 4.1.0+ 当可用时,包含指向 Test runner task 的引用。 警告:当使用全局 expect 进行并发测试时,this.task 为 undefined。请使用 context.expect,以确保在自定义断言中 task 可用。 testPath 当前测试的文件路径。 environment 当前环境的名称(例如,jsdom)。 soft 断言是否作为软断言(soft assertion)调用。你不需要手动处理,Vitest 始终会捕获错误。 assertion advanced 5.0.0+ 底层的 Chai 断言对象。这是 Chai 插件接收到的同一个实例,让你可以访问 Chai 的标志系统(flag system)和链式方法。对于需要与 Chai 内部交互的自定义断言,这非常有用。 提示:这些并非所有可用的属性,仅列出了最实用的几个。其他状态值由 Vitest 内部使用。

评论 (0)