diff --git a/docs/.vitepress/config.ts b/docs/.vitepress/config.ts index 598dc7dd29d5..e88251155018 100644 --- a/docs/.vitepress/config.ts +++ b/docs/.vitepress/config.ts @@ -132,6 +132,14 @@ export default defineConfig({ text: 'Test Context', link: '/guide/test-context', }, + { + text: 'Extending Matchers', + link: '/guide/extending-matchers', + }, + { + text: 'Snapshot Serializer', + link: '/guide/snapshot-serializer', + }, { text: 'IDE Integration', link: '/guide/ide', diff --git a/docs/api/index.md b/docs/api/index.md index 0ce24ec623c3..2b257f6013a6 100644 --- a/docs/api/index.md +++ b/docs/api/index.md @@ -9,7 +9,7 @@ type TestFunction = () => Awaitable When a test function returns a promise, the runner will wait until it is resolved to collect async expectations. If the promise is rejected, the test will fail. -::: tip +::: tip In Jest, `TestFunction` can also be of type `(done: DoneCallback) => void`. If this form is used, the test will not be concluded until `done` is called. You can achieve the same using an `async` function, see the [Migration guide Done Callback section](../guide/migration#done-callback). ::: @@ -957,7 +957,7 @@ When you use `test` in the top level of file, they are collected as part of the test('matches inline snapshot', () => { const data = { foo: new Set(['bar', 'snapshot']) } - // Vitest will updates following content when updating the snapshot + // Vitest will update following content when updating the snapshot expect(data).toMatchInlineSnapshot(` { "foo": Set { @@ -969,10 +969,22 @@ When you use `test` in the top level of file, they are collected as part of the }) ``` - +- **Type:** `(snapshot?: string) => void` + + The same as `[toMatchSnapshot](#toMatchSnapshot)`, but expects the same value as `[toThrowError](#toThrowError)`. + + If the function throws an `Error`, the snapshot will be the error message. Otherwise, snapshot will be the value thrown by the function. + +### toThrowErrorMatchingInlineSnapshot + +- **Type:** `(snapshot?: string) => void` + + The same as `[toMatchInlineSnapshot](#toMatchInlineSnapshot)`, but expects the same value as `[toThrowError](#toThrowError)`. + + If the function throws an `Error`, the snapshot will be the error message. Otherwise, snapshot will be the value thrown by the function. ### toHaveBeenCalled @@ -1203,6 +1215,27 @@ When you use `test` in the top level of file, they are collected as part of the }) ``` +### toSatisfy + + - **Type:** `(predicate: (value: any) => boolean) => Awaitable` + + This assertion checks if a value satisfies a certain predicate. + + ```ts + describe('toSatisfy()', () => { + const isOdd = (value: number) => value % 2 !== 0 + + it('pass with 0', () => { + expect(1).toSatisfy(isOdd) + }) + + it('pass with negotiation', () => { + expect(2).not.toSatisfy(isOdd) + }) + }) + ``` + + ### resolves - **Type:** `Promisify` @@ -1337,10 +1370,72 @@ When you use `test` in the top level of file, they are collected as part of the ### expect.stringContaining ### expect.not.stringContaining ### expect.stringMatching -### expect.not.stringMatching +### expect.not.stringMatching --> ### expect.addSnapshotSerializer -### expect.extend --> + +- **Type:** `(plugin: PrettyFormatPlugin) => void` + + This method adds custom serializers that are called when creating a snapshot. This is advanced feature - if you want to know more, please read a [guide on custom serializers](/guide/snapshot-serializer). + + If you are adding custom serializers, you should call this method inside [`setupFiles`](/config/#setupFiles). This will affect every snapshot. + + :::tip + If you previously used Vue CLI with Jest, you might want to install [jest-serializer-vue](https://www.npmjs.com/package/jest-serializer-vue). Otherwise, your snapshots will be wrapped in a string, which cases `"` to be escaped. + ::: + +### expect.extend + +- **Type:** `(matchers: MatchersObject) => void` + + You can extend default matchers with your own. This function is used to extend the matchers object with custom matchers. + + When you define matchers that way, you also create asymmetric matchers that can be used like `expect.stringContaining`. + + ```ts + import { expect, test } from 'vitest' + + test('custom matchers', () => { + expect.extend({ + toBeFoo: (received, expected) => { + if (received !== 'foo') { + return { + message: () => `expected ${received} to be foo`, + pass: false, + } + } + }, + }) + + expect('foo').toBeFoo() + expect({ foo: 'foo' }).toEqual({ foo: expect.toBeFoo() }) + }) + ``` + + > If you want your matchers to appear in every test, you should call this method inside [`setupFiles`](/config/#setupFiles). + + This function is compatible with Jest's `expect.extend`, so any library that uses it to create custom matchers will work with Vitest. + + If you are using TypeScript, you can extend default Matchers interface with the code bellow: + + ```ts + interface CustomMatchers { + toBeFoo(): R + } + + declare global { + namespace Vi { + interface Assertion extends CustomMatchers {} + interface AsymmetricMatchersContaining extends CustomMatchers {} + } + } + ``` + + > Note: augmenting jest.Matchers interface will also work. + + :::tip + If you want to know more, checkout [guide on extending matchers](/guide/extending-matchers). + ::: ## Setup and Teardown @@ -1375,7 +1470,7 @@ These functions allow you to hook into the life cycle of tests to avoid repeatin beforeEach(async () => { // called once before all tests run await prepareSomething() - + // clean up function, called once after all tests run return async () => { await resetSomething() diff --git a/docs/guide/extending-matchers.md b/docs/guide/extending-matchers.md new file mode 100644 index 000000000000..ff4e8c206ba3 --- /dev/null +++ b/docs/guide/extending-matchers.md @@ -0,0 +1,68 @@ +# Extending Matchers + +Since Vitest is compatible with both Chai and Jest, you can use either `chai.use` API or `expect.extend`, whichever you prefer. + +This guide will explore extending matchers with `expect.extend`. If you are interested in Chai API, check [their guide](https://www.chaijs.com/guide/plugins/). + +To extend default matchers, call `expect.extend` with an object containing your matchers. + +```ts +expect.extend({ + toBeFoo(received, expected) { + const { isNot } = this + return { + // do not alter your "pass" based on isNot. Vitest does it for you + pass: received === 'foo', + message: () => `${received} is${isNot ? ' not' : ''} foo` + } + } +}) +``` + +The return value of a matcher should be compatible with the following interface: + +```ts +interface MatcherResult { + pass: boolean + message: () => string + // If you pass these, they will automatically appear inside a diff, + // if the matcher will not pass, so you don't need to print diff yourself + actual?: unknown + expected?: unknown +} +``` + +::: warning +If you create an asynchronous matcher, don't forget to `await` the result (`await expect('foo').toBeFoo()`) in the test itself. +::: + +The first argument inside a matchers function is received value (the one inside `expect(received)`). The rest are arguments passed directly to the matcher. + +Matcher function have access to `this` context with the following properties: + +- `isNot` + + Returns true, if matcher was called on `not` (`expect(received).not.toBeFoo()`). + +- `promise` + + If matcher was called on `resolved/rejected`, this value will contain the name of modifier. Otherwise, it will be an empty string. + +- `equals` + + This is 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. + +- `utils` + + This contains a set of utility functions that you can use to display messages. + +`this` context also contains information about the current test. You can also get it by calling `expect.getState()`. The most useful properties are: + +- `currentTestName` + + Full name of the current test (including describe block). + +- `testPath` + + Path to the current test. diff --git a/docs/guide/snapshot-serializer.md b/docs/guide/snapshot-serializer.md new file mode 100644 index 000000000000..f35fa9be849b --- /dev/null +++ b/docs/guide/snapshot-serializer.md @@ -0,0 +1,43 @@ +# Snapshot Serializer + +You can add your own logic to alter how your snapshots are serialized. Like Jest, Vitest has default serializers for built-in JavaScript types, HTML elements, ImmutableJS and for React elements. + +Example serializer module: + +```ts +expect.addSnapshotSerializer({ + serialize(val, config, indentation, depth, refs, printer) { + // `printer` is a function that serializes a value using existing plugins. + return `Pretty foo: ${printer(val.foo)}` + }, + test(val) { + return val && Object.prototype.hasOwnProperty.call(val, 'foo') + }, +}) +``` + +After adding a test like this: + +```ts +test('foo snapshot test', () => { + const bar = { + foo: { + x: 1, + y: 2, + }, + } + + expect(bar).toMatchSnapshot() +}) +``` + +You will get the following snapshot: + +``` +Pretty foo: Object { + "x": 1, + "y": 2, +} +``` + +We are using Jest's `pretty-format` for serializing snapshots. You can read more about it here: [pretty-format](https://github.com/facebook/jest/blob/main/packages/pretty-format/README.md#serialize).