--- url: /guide/extending-matchers.md --- # 扩展匹配器 由于 Vitest 兼容 Chai 和 Jest,你可以使用 [`chai.use`](https://www.chaijs.com/guide/plugins/) API 或 `expect.extend`,任选其一。 本指南将探讨如何使用 `expect.extend` 扩展匹配器。如果你对 Chai 的 API 感兴趣,请查看 [他们的指南](https://www.chaijs.com/guide/plugins/)。 要扩展默认匹配器,请使用包含你的匹配器的对象调用 `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` 是接收到的值的类型。 对于同步运行的匹配器,请返回 `R`。对于普通断言,这会使返回类型为 `void`;当断言与 `.resolves`、`.rejects`、[`expect.poll`](/api/expect#poll) 或 [`expect.element`](/api/browser/assertions) 一起使用时,返回类型则为 `Promise`。当期望参数应与接收到的值具有相同类型时,可以使用 `T`: ```ts declare module 'vitest' { interface Matchers { toEqualTyped: (expected: T) => R } } ``` ::: tip 导入 `vitest` 会让 TypeScript 认为这是一个 ES 模块文件,没有它类型声明将无法工作。 ::: 扩展 `Matchers` 接口将同时为 `expect.extend`、`expect().*` 和 `expect.*` 方法添加类型。 ::: warning 别忘了在你的 `tsconfig.json` 中包含环境声明文件。 ::: 匹配器的返回值应与以下类型兼容: ```ts interface SyncMatcherResult { pass: boolean message: () => string // 如果你传递这些,当匹配器未通过时,它们将自动出现在 diff 中 // 所以你不需要自己打印 diff actual?: unknown expected?: unknown meta?: object } type MatcherResult = SyncMatcherResult | Promise ``` ::: warning 如果匹配器的实现是异步的,请将其返回类型声明为 `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 }) ``` ::: tip 要构建自定义 **快照匹配器**(`toMatchSnapshot()` / `toMatchInlineSnapshot()` / `toMatchFileSnapshot()` 的包装器),请使用从 `vitest` 导出的 `Snapshots`。参见 [自定义快照匹配器](/guide/snapshot#custom-snapshot-matchers)。 ::: 匹配器函数可以访问具有以下属性的 `this` 上下文: ## `isNot` 如果匹配器是在 `not` 上调用的 (`expect(received).not.toBeFoo()`),则返回 true。你不需要处理它,Vitest 会自动反转 `pass` 的值。 ## `promise` 如果匹配器是在 `resolved/rejected` 上调用的,此值将包含修饰符的名称。否则,它将是一个空字符串。 ## `equals` 这是一个允许你比较两个值的实用函数。如果值相等则返回 `true`,否则返回 `false`。此函数在内部几乎用于每个匹配器。默认情况下,它支持带有非对称匹配器的对象。 ## `customTesters` 传递给 `equals` 的相等性测试器,包括通过 [`expect.addEqualityTesters`](/api/expect#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` 4.1.0 {#task} 可用时包含对 [`Test` 运行器任务](/api/advanced/runner#tasks) 的引用。 ::: warning 当在并发测试中使用全局 `expect` 时,`this.task` 为 `undefined`。请使用 `context.expect` 以确保 `task` 在自定义匹配器中可用。 ::: ## `testPath` 当前测试的文件路径。 ## `environment` 当前 [`environment`](/config/environment) 的名称(例如,`jsdom`)。 ## `soft` 断言是否作为 [`soft`](/api/expect#soft) 调用。你不需要处理它,Vitest 总是会捕获错误。 ## `assertion` 5.0.0 {#assertion} 底层的 [Chai 断言](https://www.chaijs.com/guide/plugins/)对象。这是 Chai 插件接收的同一个实例,让你可以访问 Chai 的标记系统和可链式调用的方法。对于构建需要与 Chai 内部机制交互的自定义匹配器,这会很有用。 ::: tip 这些并不是所有可用的属性,只是最有用的那些。其他状态值由 Vitest 内部使用。 :::