Skip to content

Commit

Permalink
[RFC] Inline Snapshots (#6380)
Browse files Browse the repository at this point in the history
* [RFC] Inline Snapshots

* Fix typechecking and refactor

* Refactor State#match

* Fix stacktrace regression

* Remove unused argument

* Fix support for property matchers

* Fix tests after State#match refactor

* Fix toThrowErrorMatchingInlineSnapshot

* Optimize snapshot lookup

* Support flow parser

* Relax prettier version requirement

* Fix TypeScript support

* Support toMatchInlineSnapshot from external files

* Add tests for saveInlineSnapshots()

* Fix InlineSnapshot type definition

* Escape backtick strings

* Add test for escaping backticks

* Code review fixes

* Support project option for prettier

Massive refactor, sorry I didn't split the commits

* Refactor configuration

* Fix typechecking

* Set default value in cli/options

* Fix typechecking

* Write E2E tests for toMatchInlineSnapshot

* Fix bad copy/paste

* Parameterize saveInlineSnapshots test with jest-each

* Write E2E tests for toThrowErrorMatchingInlineSnapshot

* Write more aync tests

* Skip currently failing test

* Remove unused testPath property from State

* Write documentation

* Support async matchers

* Fix links in docs
  • Loading branch information
azz authored and cpojer committed Jun 28, 2018
1 parent 393e971 commit d3a6a74
Show file tree
Hide file tree
Showing 36 changed files with 1,145 additions and 74 deletions.
4 changes: 4 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
## master

### Features

- `[jest-snapshot]` Introduce `toMatchInlineSnapshot` and `toThrowErrorMatchingInlineSnapshot` matchers ([#6380](https://github.com/facebook/jest/pull/6380))

### Chore & Maintenance

- `[website]` Switch domain to https://jestjs.io ([#6549](https://github.com/facebook/jest/pull/6549))
Expand Down
1 change: 1 addition & 0 deletions TestUtils.js
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ const DEFAULT_PROJECT_CONFIG: ProjectConfig = {
modulePathIgnorePatterns: [],
modulePaths: [],
name: 'test_name',
prettier: 'prettier',
resetMocks: false,
resetModules: false,
resolver: null,
Expand Down
6 changes: 6 additions & 0 deletions docs/Configuration.md
Original file line number Diff line number Diff line change
Expand Up @@ -402,6 +402,12 @@ Presets may also be relative filesystem paths.
}
```

### `prettier` [string]

Default: `'prettier'`

Sets the path to the [`prettier`](https://prettier.io/) node module used to update inline snapshots.

### `projects` [array<string | ProjectConfig>]

Default: `undefined`
Expand Down
14 changes: 13 additions & 1 deletion docs/ExpectAPI.md
Original file line number Diff line number Diff line change
Expand Up @@ -1025,12 +1025,18 @@ test('this house has my desired features', () => {

This ensures that a value matches the most recent snapshot. Check out [the Snapshot Testing guide](SnapshotTesting.md) for more information.

The optional propertyMatchers argument allows you to specify asymmetric matchers which are verified instead of the exact values.
The optional `propertyMatchers` argument allows you to specify asymmetric matchers which are verified instead of the exact values.

The last argument allows you option to specify a snapshot name. Otherwise, the name is inferred from the test.

_Note: While snapshot testing is most commonly used with React components, any serializable value can be used as a snapshot._

### `.toMatchInlineSnapshot(propertyMatchers, inlineSnapshot)`

Ensures that a value matches the most recent snapshot. Unlike [`.toMatchSnapshot()`](#tomatchsnapshotpropertymatchers-snapshotname), the snapshots will be written to the current source file, inline.

Check out the section on [Inline Snapshots](./SnapshotTesting.md#inline-snapshots) for more info.

### `.toStrictEqual(value)`

Use `.toStrictEqual` to test that objects have the same types as well as structure.
Expand Down Expand Up @@ -1134,3 +1140,9 @@ exports[`drinking flavors throws on octopus 1`] = `"yuck, octopus flavor"`;
```

Check out [React Tree Snapshot Testing](http://facebook.github.io/jest/blog/2016/07/27/jest-14.html) for more information on snapshot testing.

### `.toThrowErrorMatchingInlineSnapshot()`

This matcher is much like [`.toThrowErrorMatchingSnapshot`](#tothrowerrormatchingsnapshot), except instead of writing the snapshot value to a `.snap` file, it will be written into the source code automatically.

Check out the section on [Inline Snapshots](./SnapshotTesting.md#inline-snapshots) for more info.
43 changes: 43 additions & 0 deletions docs/SnapshotTesting.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,49 @@ Once you're finished, Jest will give you a summary before returning back to watc

![](/img/content/interactiveSnapshotDone.png)

### Inline Snapshots

Inline snapshots behave identically to external snapshots (`.snap` files), except the snapshot values are written automatically back into the source code. This means you can get the benefits of automatically generated snapshots without having to switch to an external file to make sure the correct value was written.

> Inline snapshots are powered by [Prettier](https://prettier.io). To use inline snapshots you must have `prettier` installed in your project. Your Prettier configuration will be respected when writing to test files.
>
> If you have `prettier` installed in a location where Jest can't find it, you can tell Jest how to find it using the [`"prettier"`](./Configuration.md#prettier-string) configuration property.
**Example:**

First, you write a test, calling `.toMatchInlineSnapshot()` with no arguments:

```javascript
it('renders correctly', () => {
const tree = renderer
.create(<Link page="https://prettier.io">Prettier</Link>)
.toJSON();
expect(tree).toMatchInlineSnapshot();
});
```

The next time you run Jest, `tree` will be evaluated, and a snapshot will be written as an argument to `toMatchInlineSnapshot`:

```javascript
it('renders correctly', () => {
const tree = renderer
.create(<Link page="https://prettier.io">Prettier</Link>)
.toJSON();
expect(tree).toMatchInlineSnapshot(`
<a
className="normal"
href="https://prettier.io"
onMouseEnter={[Function]}
onMouseLeave={[Function]}
>
Prettier
</a>
`);
});
```

That's all there is to it! You can even update the snapshots with `--updateSnapshot` or using the `u` key in `--watch` mode.

### Property Matchers

Often there are fields in the object you want to snapshot which are generated (like IDs and Dates). If you try to snapshot these objects, they will force the snapshot to fail on every run:
Expand Down
1 change: 1 addition & 0 deletions e2e/__tests__/__snapshots__/show_config.test.js.snap
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ exports[`--showConfig outputs config info and exits 1`] = `
\\"moduleNameMapper\\": {},
\\"modulePathIgnorePatterns\\": [],
\\"name\\": \\"[md5 hash]\\",
\\"prettier\\": null,
\\"resetMocks\\": false,
\\"resetModules\\": false,
\\"resolver\\": null,
Expand Down
115 changes: 115 additions & 0 deletions e2e/__tests__/__snapshots__/to_match_inline_snapshot.test.js.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`basic support: initial write 1`] = `
"test('inline snapshots', () =>
expect({apple: 'original value'}).toMatchInlineSnapshot(\`
Object {
\\"apple\\": \\"original value\\",
}
\`));
"
`;

exports[`basic support: snapshot mismatch 1`] = `
"test('inline snapshots', () =>
expect({apple: 'updated value'}).toMatchInlineSnapshot(\`
Object {
\\"apple\\": \\"original value\\",
}
\`));
"
`;

exports[`basic support: snapshot passed 1`] = `
"test('inline snapshots', () =>
expect({apple: 'original value'}).toMatchInlineSnapshot(\`
Object {
\\"apple\\": \\"original value\\",
}
\`));
"
`;

exports[`basic support: snapshot updated 1`] = `
"test('inline snapshots', () =>
expect({apple: 'updated value'}).toMatchInlineSnapshot(\`
Object {
\\"apple\\": \\"updated value\\",
}
\`));
"
`;

exports[`handles property matchers: initial write 1`] = `
"test('handles property matchers', () => {
expect({createdAt: new Date()}).toMatchInlineSnapshot(
{createdAt: expect.any(Date)},
\`
Object {
\\"createdAt\\": Any<Date>,
}
\`,
);
});
"
`;
exports[`handles property matchers: snapshot failed 1`] = `
"test('handles property matchers', () => {
expect({createdAt: \\"string\\"}).toMatchInlineSnapshot(
{createdAt: expect.any(Date)},
\`
Object {
\\"createdAt\\": Any<Date>,
}
\`,
);
});
"
`;
exports[`handles property matchers: snapshot passed 1`] = `
"test('handles property matchers', () => {
expect({createdAt: new Date()}).toMatchInlineSnapshot(
{createdAt: expect.any(Date)},
\`
Object {
\\"createdAt\\": Any<Date>,
}
\`,
);
});
"
`;
exports[`handles property matchers: snapshot updated 1`] = `
"test('handles property matchers', () => {
expect({createdAt: 'string'}).toMatchInlineSnapshot(
{createdAt: expect.any(String)},
\`
Object {
\\"createdAt\\": Any<String>,
}
\`,
);
});
"
`;
exports[`supports async matchers 1`] = `
"test('inline snapshots', async () => {
expect(Promise.resolve('success')).resolves.toMatchInlineSnapshot(
\`\\"success\\"\`,
);
expect(Promise.reject('fail')).rejects.toMatchInlineSnapshot(\`\\"fail\\"\`);
});
"
`;
exports[`supports async tests 1`] = `
"test('inline snapshots', async () => {
await 'next tick';
expect(42).toMatchInlineSnapshot(\`42\`);
});
"
`;
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should support rejecting promises 1`] = `
"test('should support rejecting promises', async () => {
await expect(
Promise.reject(new Error('octopus')),
).rejects.toThrowErrorMatchingInlineSnapshot(\`\\"octopus\\"\`);
});
"
`;
exports[`updates existing snapshot: updated snapshot 1`] = `
"test('updates existing snapshot', () => {
expect(() => {
throw new Error('apple');
}).toThrowErrorMatchingInlineSnapshot(\`\\"apple\\"\`);
});
"
`;
exports[`works fine when function throws error: initial write 1`] = `
"test('works fine when function throws error', () => {
expect(() => {
throw new Error('apple');
}).toThrowErrorMatchingInlineSnapshot(\`\\"apple\\"\`);
});
"
`;
Loading

0 comments on commit d3a6a74

Please sign in to comment.