Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Document and test custom, async, inline snapshot matcher #10922

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 35 additions & 0 deletions docs/ExpectAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if Jest itself could instantiate an error and store it before calling this function? So that particular gotcha is gone. Not needed for now of course, and documenting for Jest 25 and 26 is great regardless

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, I did some shallow debugging but couldn't really figure out why it was working without this.error = new Error() for existing snapshots but not for new snapshots. I might revisit this though having a test in the repo is a sufficient guarantee for me now 😉

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:
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
`;
27 changes: 27 additions & 0 deletions e2e/__tests__/customInlineSnapshotMatchers.test.ts
Original file line number Diff line number Diff line change
@@ -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();
});
33 changes: 33 additions & 0 deletions e2e/custom-inline-snapshot-matchers/__tests__/asynchronous.test.js
Original file line number Diff line number Diff line change
@@ -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 #?"`,
);
});
5 changes: 5 additions & 0 deletions e2e/custom-inline-snapshot-matchers/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"jest": {
"testEnvironment": "node"
}
}
35 changes: 35 additions & 0 deletions website/versioned_docs/version-25.x/ExpectAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand Down