···25252626If you are using TypeScript, you can extend default `Assertion` interface in an ambient declaration file (e.g: `vitest.d.ts`) with the code below:
27272828-```ts
2828+::: code-group
2929+```ts [<Version>3.2.0</Version>]
3030+import 'vitest'
3131+3232+interface CustomMatchers<R = unknown> {
3333+ toBeFoo: () => R
3434+}
3535+3636+declare module 'vitest' {
3737+ interface Matchers<T = any> extends CustomMatchers<T> {}
3838+}
3939+```
4040+```ts [<Version>3.0.0</Version>]
2941import 'vitest'
30423143interface CustomMatchers<R = unknown> {
···3749 interface AsymmetricMatchersContaining extends CustomMatchers {}
3850}
3951```
5252+:::
5353+5454+::: tip
5555+Since Vitest 3.2, you can extend the `Matchers` interface to have type-safe assertions in `expect.extend`, `expect().*`, and `expect.*` methods at the same time. Previously, you had to define separate interfaces for each of them.
5656+:::
40574158::: warning
4259Don't forget to include the ambient declaration file in your `tsconfig.json`.
···5673```
57745875::: warning
5959-If you create an asynchronous matcher, don't forget to `await` the result (`await expect('foo').toBeFoo()`) in the test itself.
7676+If you create an asynchronous matcher, don't forget to `await` the result (`await expect('foo').toBeFoo()`) in the test itself::
7777+7878+```ts
7979+expect.extend({
8080+ async toBeAsyncAssertion() {
8181+ // ...
8282+ }
8383+})
8484+8585+await expect().toBeAsyncAssertion()
8686+```
6087:::
61886289The first argument inside a matcher's function is the received value (the one inside `expect(received)`). The rest are arguments passed directly to the matcher.
63906464-Matcher function have access to `this` context with the following properties:
9191+Matcher function has access to `this` context with the following properties:
65926666-- `isNot`
9393+### `isNot`
67946868- Returns true, if matcher was called on `not` (`expect(received).not.toBeFoo()`).
9595+Returns true, if matcher was called on `not` (`expect(received).not.toBeFoo()`).
69967070-- `promise`
9797+### `promise`
71987272- If matcher was called on `resolved/rejected`, this value will contain the name of modifier. Otherwise, it will be an empty string.
9999+If matcher was called on `resolved/rejected`, this value will contain the name of modifier. Otherwise, it will be an empty string.
731007474-- `equals`
101101+### `equals`
751027676- This is a utility function that allows you to compare two values. It will return `true` if values are equal, `false` otherwise. This function is used internally for almost every matcher. It supports objects with asymmetric matchers by default.
103103+This is a utility function that allows you to compare two values. It will return `true` if values are equal, `false` otherwise. This function is used internally for almost every matcher. It supports objects with asymmetric matchers by default.
771047878-- `utils`
105105+### `utils`
791068080- This contains a set of utility functions that you can use to display messages.
107107+This contains a set of utility functions that you can use to display messages.
8110882109`this` context also contains information about the current test. You can also get it by calling `expect.getState()`. The most useful properties are:
831108484-- `currentTestName`
111111+### `currentTestName`
851128686- Full name of the current test (including describe block).
113113+Full name of the current test (including describe block).
871148888-- `testPath`
115115+### `testPath`
891169090- Path to the current test.
117117+Path to the current test.
+13-4
packages/expect/src/types.ts
···86868787export type ExpectationResult = SyncExpectationResult | AsyncExpectationResult
88888989-export interface RawMatcherFn<T extends MatcherState = MatcherState> {
9090- (this: T, received: any, ...expected: Array<any>): ExpectationResult
8989+export interface RawMatcherFn<T extends MatcherState = MatcherState, E extends Array<any> = Array<any>> {
9090+ (this: T, received: any, ...expected: E): ExpectationResult
9191}
9292+9393+// Allow unused `T` to preserve its name for extensions.
9494+// Type parameter names must be identical when extending those types.
9595+// eslint-disable-next-line
9696+export interface Matchers<T = any> {}
92979398export type MatchersObject<T extends MatcherState = MatcherState> = Record<
9499 string,
95100 RawMatcherFn<T>
9696-> & ThisType<T>
101101+> & ThisType<T> & {
102102+ [K in keyof Matchers<T>]?: RawMatcherFn<T, Parameters<Matchers<T>[K]>>
103103+}
9710498105export interface ExpectStatic
99106 extends Chai.ExpectStatic,
107107+ Matchers,
100108 AsymmetricMatchersContaining {
101109 <T>(actual: T, message?: string): Assertion<T>
102110 extend: (expects: MatchersObject) => void
···639647640648export interface Assertion<T = any>
641649 extends VitestAssertion<Chai.Assertion, T>,
642642- JestAssertion<T> {
650650+ JestAssertion<T>,
651651+ Matchers<T> {
643652 /**
644653 * Ensures a value is of a specific type.
645654 *
+52
test/typescript/test-d/expect-extend.test-d.ts
···11+import { expect, expectTypeOf, test } from 'vitest'
22+33+interface CustomMatchers<R = unknown> {
44+ toMatchSchema: (schema: { a: string }) => R
55+ toEqualMultiple: (a: string, b: number) => R
66+}
77+88+declare module 'vitest' {
99+ interface Matchers<T = any> extends CustomMatchers<T> {}
1010+}
1111+1212+test('infers matcher declaration type from a custom matcher type', () => {
1313+ expect.extend({
1414+ toMatchSchema(received, expected) {
1515+ expectTypeOf(received).toBeAny()
1616+ expectTypeOf(expected).toEqualTypeOf<{ a: string }>()
1717+1818+ return { pass: true, message: () => '' }
1919+ },
2020+ toEqualMultiple(received, a, b) {
2121+ expectTypeOf(received).toBeAny()
2222+ expectTypeOf(a).toBeString()
2323+ expectTypeOf(b).toBeNumber()
2424+2525+ return { pass: true, message: () => '' }
2626+ },
2727+ })
2828+2929+ expect({ a: 1, b: '2' }).toMatchSchema({ a: '1' })
3030+ expect('a').toEqualMultiple('a', 1)
3131+})
3232+3333+test('automatically extends asymmetric matchers', () => {
3434+ expect({}).toEqual({
3535+ nestedSchema: expect.toMatchSchema({
3636+ a: '1',
3737+ // @ts-expect-error Unknown property.
3838+ b: 2,
3939+ }),
4040+ })
4141+})
4242+4343+test('treats matcher declarations as optional', () => {
4444+ expect.extend(
4545+ /**
4646+ * @note Although annotated, you don't have to declare matchers.
4747+ * You can call `expect.extend()` multiple times or get the matcher
4848+ * declarations from a third-party library.
4949+ */
5050+ {},
5151+ )
5252+})