扩展匹配器
由于 Vitest 兼容 Chai 和 Jest,所以可以根据个人喜好使用 chai.use API 或者 expect.extend。
本文将以 expect.extend 为例探讨扩展匹配器。如果你对 Chai 的 API 更感兴趣,可以查看 他们的指南。
为了扩展默认的断言,可以使用对象包裹断言的形式调用 expect.extend 方法。
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 接口:
import 'vitest'
declare module 'vitest' {
interface Matchers<R, T> {
toBeFoo: () => R
}
}R 表示断言的返回类型,T 表示接收值的类型。
同步执行的匹配器应返回 R。对于普通断言,返回类型为 void;与 .resolves、.rejects、expect.poll 或 expect.element 一起使用时,返回类型则为 Promise<void>。如果预期值参数需要与接收值保持相同类型,可以使用 T:
declare module 'vitest' {
interface Matchers<R, T> {
toEqualTyped: (expected: T) => R
}
}提示
导入 vitest 会使 TypeScript 将其视为 ES 模块文件,若无此声明则类型检查将无法生效。
扩展 Matchers 接口,让 expect.extend、expect().* 和 expect.* 方法同时具备类型安全的断言支持。
注意
不要忘记在 tsconfig.json 中包含声明文件。
断言的返回值应该兼容如下类型:
interface SyncMatcherResult {
pass: boolean
message: () => string
// 如果你传了这些参数,它们将自动出现在 diff 信息中,
// 所以即便断言不通过,你也不必自己输出 diff
actual?: unknown
expected?: unknown
meta?: object
}
type MatcherResult = SyncMatcherResult | Promise<SyncMatcherResult>注意
如果匹配器采用异步实现,请将其返回类型声明为 Promise<void>,而不是 R,并且不要忘记在测试中使用 await:
expect.extend({
async toBeAsyncAssertion(received) {
return {
pass: received === 'foo',
message: () => `expected ${received} to be foo`,
}
}
})
declare module 'vitest' {
interface Matchers<R, T> {
toBeAsyncAssertion: () => Promise<void>
}
}
await expect('foo').toBeAsyncAssertion()匹配器的第一个参数是接收值,即 expect(received) 中的 received,其余参数会直接传递给匹配器。自 4.1 起,Vitest 提供了多个类型供自定义匹配器使用:
import type {
// 函数类型
Matcher,
// 返回值
MatcherResult,
// 通过 `this` 访问的状态
MatcherState,
} from 'vitest'
import { expect } from 'vitest'
// 使用 "function" 关键字定义以访问 "this" 的简单匹配器
const customMatcher: Matcher = function (received) {
// ...
}
// 带参数的匹配器
const customMatcher: Matcher<MatcherState, [arg1: unknown, arg2: unknown]> = 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。请参阅 自定义快照匹配器。
断言方法可以访问上下文 this 对象中的这些属性:
isNot
如果断言是在 not 方法上调用的(expect(received).not.toBeFoo()),则返回 true。你无需手动处理该逻辑,Vitest 会自动反转 pass 的值。
promise
如果断言是在 resolved/rejected 中调用的,它的值将包含此断言的名称。否则,它将是一个空字符串。
equals
这是一个工具函数,他可以帮助你比较两个值。如果是相同的则返回 true,反之返回 false。这个方法几乎在每个断言内部都有使用。默认情况下,它支持非对称的断言。
customTesters
Equality testers to pass to equals, including ones registered with expect.addEqualityTesters.
equals alone does not compare the contents of Map and Set. To get the same equality as toEqual, add this.utils.iterableEquality:
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 高级 API 4.1.0+
包含对 测试运行器任务 的引用(如果可用)。
注意
当在并发测试中使用全局 expect 时,this.task 为 undefined。请改用 context.expect 以确保 task 在自定义匹配器中可用。
testPath
当前正在执行的测试文件路径。
environment
当前 environment 的名称(例如 jsdom)。
soft
断言是否以 soft 方式调用。你无需手动处理该逻辑,Vitest 始终会捕获错误。
assertion 高级 API 5.0.0+
底层 Chai 断言 对象。这是与 Chai 插件接收的同一个实例,使你能够访问 Chai 的参数系统和可链式方法。适用于需要与 Chai 内部交互的自定义匹配器。
提示
以上并非全部可用属性,仅列出最实用的部分。其他状态值由 Vitest 内部使用。