diff --git a/docs/ExpectAPI.md b/docs/ExpectAPI.md index 2ee488f23e8b..d1ce4d6dbf84 100644 --- a/docs/ExpectAPI.md +++ b/docs/ExpectAPI.md @@ -255,6 +255,41 @@ it('stores only 10 characters', () => { }); ``` +#### async + +If your custom inline snapshot matcher is async i.e. uses `async`-`await` you might encounter an error like "Multiple inline snapshots for the same call are not supported". Jest needs additional context information to find where the custom inline snapshot matcher was used to update the snapshots properly. + +```js +const {toMatchInlineSnapshot} = require('jest-snapshot'); + +expect.extend({ + async toMatchObservationInlineSnapshot(fn, ...rest) { + // The error (and its stacktrace) must be created before any `await` + this.error = new Error(); + + // The implementation of `observe` doesn't matter. + // It only matters that the custom snapshot matcher is async. + const observation = await observe(async () => { + await fn(); + }); + + return toMatchInlineSnapshot.call(this, recording, ...rest); + }, +}); + +it('observes something', async () => { + await expect(async () => { + return 'async action'; + }).toMatchTrimmedInlineSnapshot(); + /* + The snapshot will be added inline like + await expect(async () => { + return 'async action'; + }).toMatchTrimmedInlineSnapshot(`"async action"`); + */ +}); +``` + ### `expect.anything()` `expect.anything()` matches anything but `null` or `undefined`. You can use it inside `toEqual` or `toBeCalledWith` instead of a literal value. For example, if you want to check that a mock function is called with a non-null argument: diff --git a/e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap b/e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap new file mode 100644 index 000000000000..eebb3e9ff410 --- /dev/null +++ b/e2e/__tests__/__snapshots__/customInlineSnapshotMatchers.test.ts.snap @@ -0,0 +1,93 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`works with custom inline snapshot matchers 1`] = ` +FAIL __tests__/asynchronous.test.js + ✕ new async, inline snapshots + ✕ mismatching async, inline snapshots + + ● new async, inline snapshots + + expect(received).toMatchInlineSnapshot() + + Snapshot name: \`new async, inline snapshots 1\` + + New snapshot was not written. The update flag must be explicitly passed to write a new snapshot. + + This is likely because this test is run in a continuous integration (CI) environment in which snapshots are not written by default. + + Received: "result #1" + + 20 | + 21 | test('new async, inline snapshots', async () => { + > 22 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot(); + | ^ + 23 | await expect(async () => 'result #2').toMatchObservationInlineSnapshot(); + 24 | }); + 25 | + + at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:22:41) + + ● new async, inline snapshots + + expect(received).toMatchInlineSnapshot() + + Snapshot name: \`new async, inline snapshots 2\` + + New snapshot was not written. The update flag must be explicitly passed to write a new snapshot. + + This is likely because this test is run in a continuous integration (CI) environment in which snapshots are not written by default. + + Received: "result #2" + + 21 | test('new async, inline snapshots', async () => { + 22 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot(); + > 23 | await expect(async () => 'result #2').toMatchObservationInlineSnapshot(); + | ^ + 24 | }); + 25 | + 26 | test('mismatching async, inline snapshots', async () => { + + at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:23:41) + + ● mismatching async, inline snapshots + + expect(received).toMatchInlineSnapshot(snapshot) + + Snapshot name: \`mismatching async, inline snapshots 1\` + + Snapshot: "result #?" + Received: "result #1" + + 25 | + 26 | test('mismatching async, inline snapshots', async () => { + > 27 | await expect(async () => 'result #1').toMatchObservationInlineSnapshot( + | ^ + 28 | \`"result #?"\`, + 29 | ); + 30 | await expect(async () => 'result #2').toMatchObservationInlineSnapshot( + + at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:27:41) + + ● mismatching async, inline snapshots + + expect(received).toMatchInlineSnapshot(snapshot) + + Snapshot name: \`mismatching async, inline snapshots 2\` + + Snapshot: "result #?" + Received: "result #2" + + 28 | \`"result #?"\`, + 29 | ); + > 30 | await expect(async () => 'result #2').toMatchObservationInlineSnapshot( + | ^ + 31 | \`"result #?"\`, + 32 | ); + 33 | }); + + at Object.toMatchObservationInlineSnapshot (__tests__/asynchronous.test.js:30:41) + + › 4 snapshots failed. +Snapshot Summary + › 4 snapshots failed from 1 test suite. Inspect your code changes or re-run jest with \`-u\` to update them. +`; diff --git a/e2e/__tests__/customInlineSnapshotMatchers.test.ts b/e2e/__tests__/customInlineSnapshotMatchers.test.ts new file mode 100644 index 000000000000..a644c74ab270 --- /dev/null +++ b/e2e/__tests__/customInlineSnapshotMatchers.test.ts @@ -0,0 +1,27 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ + +import {wrap} from 'jest-snapshot-serializer-raw'; +import {extractSummary} from '../Utils'; +import runJest from '../runJest'; + +test('works with custom inline snapshot matchers', () => { + const {stderr} = runJest('custom-inline-snapshot-matchers', [ + // Prevent adding new snapshots or rather changing the test. + '--ci', + 'asynchronous.test.js', + ]); + + let {rest} = extractSummary(stderr); + + rest = rest + .split('\n') + .filter(line => line.indexOf('at Error (native)') < 0) + .join('\n'); + + expect(wrap(rest)).toMatchSnapshot(); +}); diff --git a/e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js b/e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js new file mode 100644 index 000000000000..afb94f944e42 --- /dev/null +++ b/e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js @@ -0,0 +1,33 @@ +/** + * Copyright (c) Facebook, Inc. and its affiliates. All Rights Reserved. + * + * This source code is licensed under the MIT license found in the + * LICENSE file in the root directory of this source tree. + */ +const {toMatchInlineSnapshot} = require('jest-snapshot'); + +expect.extend({ + async toMatchObservationInlineSnapshot(fn, ...args) { + this.error = new Error(); + // This specific behavior can be implemented without a custom matcher. + // In a real example one might want to observe some global value that `fn()` is affecting. + // The difference between before and after `fn()` might then be persisted as a snapshot. + const result = await fn(); + + return toMatchInlineSnapshot.call(this, result, ...args); + }, +}); + +test('new async, inline snapshots', async () => { + await expect(async () => 'result #1').toMatchObservationInlineSnapshot(); + await expect(async () => 'result #2').toMatchObservationInlineSnapshot(); +}); + +test('mismatching async, inline snapshots', async () => { + await expect(async () => 'result #1').toMatchObservationInlineSnapshot( + `"result #?"`, + ); + await expect(async () => 'result #2').toMatchObservationInlineSnapshot( + `"result #?"`, + ); +}); diff --git a/e2e/custom-inline-snapshot-matchers/package.json b/e2e/custom-inline-snapshot-matchers/package.json new file mode 100644 index 000000000000..148788b25446 --- /dev/null +++ b/e2e/custom-inline-snapshot-matchers/package.json @@ -0,0 +1,5 @@ +{ + "jest": { + "testEnvironment": "node" + } +} diff --git a/website/versioned_docs/version-25.x/ExpectAPI.md b/website/versioned_docs/version-25.x/ExpectAPI.md index c3e8ee6a6284..d20578d74b6f 100644 --- a/website/versioned_docs/version-25.x/ExpectAPI.md +++ b/website/versioned_docs/version-25.x/ExpectAPI.md @@ -256,6 +256,41 @@ it('stores only 10 characters', () => { }); ``` +#### async + +If your custom inline snapshot matcher is async i.e. uses `async`-`await` you might encounter an error like "Multiple inline snapshots for the same call are not supported". Jest needs additional context information to find where the custom inline snapshot matcher was used to update the snapshots properly. + +```js +const {toMatchInlineSnapshot} = require('jest-snapshot'); + +expect.extend({ + async toMatchObservationInlineSnapshot(fn, ...rest) { + // The error (and its stacktrace) must be created before any `await` + this.error = new Error(); + + // The implementation of `observe` doesn't matter. + // It only matters that the custom snapshot matcher is async. + const observation = await observe(async () => { + await fn(); + }); + + return toMatchInlineSnapshot.call(this, recording, ...rest); + }, +}); + +it('observes something', async () => { + await expect(async () => { + return 'async action'; + }).toMatchTrimmedInlineSnapshot(); + /* + The snapshot will be added inline like + await expect(async () => { + return 'async action'; + }).toMatchTrimmedInlineSnapshot(`"async action"`); + */ +}); +``` + ### `expect.anything()` `expect.anything()` matches anything but `null` or `undefined`. You can use it inside `toEqual` or `toBeCalledWith` instead of a literal value. For example, if you want to check that a mock function is called with a non-null argument: