Skip to content

Commit 9b87036

Browse files
committed
test_runner: add snapshot testing
This commit adds a t.assert.snapshot() method that implements snapshot testing. Serialization uses JSON.stringify() by default, but users can configure the serialization to meet their needs. Fixes: #48260
1 parent 6347b7e commit 9b87036

File tree

14 files changed

+824
-4
lines changed

14 files changed

+824
-4
lines changed

doc/api/cli.md

+23
Original file line numberDiff line numberDiff line change
@@ -991,6 +991,16 @@ added: REPLACEME
991991
992992
Enable module mocking in the test runner.
993993

994+
### `--experimental-test-snapshots`
995+
996+
<!-- YAML
997+
added: REPLACEME
998+
-->
999+
1000+
> Stability: 1.0 - Early development
1001+
1002+
Enable [snapshot testing][] in the test runner.
1003+
9941004
### `--experimental-vm-modules`
9951005

9961006
<!-- YAML
@@ -2129,6 +2139,18 @@ added:
21292139
A number of milliseconds the test execution will fail after. If unspecified,
21302140
subtests inherit this value from their parent. The default value is `Infinity`.
21312141

2142+
### `--test-update-snapshots`
2143+
2144+
<!-- YAML
2145+
added: REPLACEME
2146+
-->
2147+
2148+
> Stability: 1.0 - Early development
2149+
2150+
Regenerates the snapshot file used by the test runner for [snapshot testing][].
2151+
Node.js must be started with the `--experimental-test-snapshots` flag in order
2152+
to use this functionality.
2153+
21322154
### `--throw-deprecation`
21332155

21342156
<!-- YAML
@@ -3269,6 +3291,7 @@ node --stack-trace-limit=12 -p -e "Error.stackTraceLimit" # prints 12
32693291
[security warning]: #warning-binding-inspector-to-a-public-ipport-combination-is-insecure
32703292
[semi-space]: https://www.memorymanagement.org/glossary/s.html#semi.space
32713293
[single executable application]: single-executable-applications.md
3294+
[snapshot testing]: test.md#snapshot-testing
32723295
[test reporters]: test.md#test-reporters
32733296
[timezone IDs]: https://en.wikipedia.org/wiki/List_of_tz_database_time_zones
32743297
[tracking issue for user-land snapshots]: https://github.com/nodejs/node/issues/44014

doc/api/test.md

+140
Original file line numberDiff line numberDiff line change
@@ -920,6 +920,61 @@ test('runs timers as setTime passes ticks', (context) => {
920920
});
921921
```
922922

923+
## Snapshot testing
924+
925+
> Stability: 1.0 - Early development
926+
927+
Snapshot tests allow arbitrary values to be serialized into string values and
928+
compared against a set of known good values. The known good values are known as
929+
snapshots, and are stored in a snapshot file. Snapshot files are managed by the
930+
test runner, but are designed to be human readable to aid in debugging. Best
931+
practice is for snapshot files to be checked into source control along with your
932+
test files. In order to enable snapshot testing, Node.js must be started with
933+
the [`--experimental-test-snapshots`][] command-line flag.
934+
935+
Snapshot files are generated by starting Node.js with the
936+
[`--test-update-snapshots`][] command-line flag. A separate snapshot file is
937+
generated for each test file. By default, the snapshot file has the same name
938+
as `process.argv[1]` with a `.snapshot` file extension. This behavior can be
939+
configured using the `config.setResolveSnapshotPath()` function. Each
940+
snapshot assertion corresponds to an export in the snapshot file.
941+
942+
An example snapshot test is shown below. The first time this test is executed,
943+
it will fail because the corresponding snapshot file does not exist.
944+
945+
```js
946+
// test.js
947+
suite('suite of snapshot tests', () => {
948+
test('snapshot test', (t) => {
949+
t.assert.snapshot({ value1: 1, value2: 2 });
950+
t.assert.snapshot(5);
951+
});
952+
});
953+
```
954+
955+
Generate the snapshot file by running the test file with
956+
`--test-update-snapshots`. The test should pass, and a file named
957+
`test.js.snapshot` is created in the same directory as the test file. The
958+
contents of the snapshot file are shown below. Each snapshot is identified by
959+
the full name of test and a counter to differentiate between snapshots in the
960+
same test.
961+
962+
```js
963+
exports[`suite of snapshot tests > snapshot test 1`] = `
964+
{
965+
"value1": 1,
966+
"value2": 2
967+
}
968+
`;
969+
970+
exports[`suite of snapshot tests > snapshot test 2`] = `
971+
5
972+
`;
973+
```
974+
975+
Once the snapshot file is created, run the tests again without the
976+
`--test-update-snapshots` flag. The tests should pass now.
977+
923978
## Test reporters
924979

925980
<!-- YAML
@@ -1622,6 +1677,54 @@ describe('tests', async () => {
16221677
});
16231678
```
16241679

1680+
## `config`
1681+
1682+
<!-- YAML
1683+
added: REPLACEME
1684+
-->
1685+
1686+
> Stability: 1.0 - Early development
1687+
1688+
An object whose methods are used to configure global test runner settings in the
1689+
current process. It is possible to apply the same configuration to all files by
1690+
placing common configuration code in a module preloaded with `--require` or
1691+
`--import`.
1692+
1693+
### `config.setDefaultSnapshotSerializers(serializers)`
1694+
1695+
<!-- YAML
1696+
added: REPLACEME
1697+
-->
1698+
1699+
> Stability: 1.0 - Early development
1700+
1701+
* `serializers` {Array} An array of synchronous functions used as the default
1702+
serializers for snapshot tests.
1703+
1704+
This function is used to customize the default serialization mechanism used by
1705+
the test runner. By default, the test runner performs serialization by calling
1706+
`JSON.stringify(value, null, 2)` on the provided value. `JSON.stringify()` does
1707+
have limitations regarding circular structures and supported data types. If a
1708+
more robust serialization mechanism is required, this function should be used.
1709+
1710+
### `config.setResolveSnapshotPath(fn)`
1711+
1712+
<!-- YAML
1713+
added: REPLACEME
1714+
-->
1715+
1716+
> Stability: 1.0 - Early development
1717+
1718+
* `fn` {Function} A function used to compute the location of the snapshot file.
1719+
The function receives the path of the test file as its only argument. If the
1720+
`process.argv[1]` is not associated with a file (for example in the REPL),
1721+
the input is undefined. `fn()` must return a string specifying the location of
1722+
the snapshot file.
1723+
1724+
This function is used to customize the location of the snapshot file used for
1725+
snapshot testing. By default, the snapshot filename is the same as the entry
1726+
point filename with a `.snapshot` file extension.
1727+
16251728
## Class: `MockFunctionContext`
16261729

16271730
<!-- YAML
@@ -3042,6 +3145,41 @@ test('test', (t) => {
30423145
});
30433146
```
30443147

3148+
#### `context.assert.snapshot(value[, options])`
3149+
3150+
<!-- YAML
3151+
added: REPLACEME
3152+
-->
3153+
3154+
> Stability: 1.0 - Early development
3155+
3156+
* `value` {any} A value to serialize to a string. If Node.js was started with
3157+
the [`--test-update-snapshots`][] flag, the serialized value is written to
3158+
the snapshot file. Otherwise, the serialized value is compared to the
3159+
corresponding value in the existing snapshot file.
3160+
* `options` {Object} Optional configuration options. The following properties
3161+
are supported:
3162+
* `serializers` {Array} An array of synchronous functions used to serialize
3163+
`value` into a string. `value` is passed as the only argument to the first
3164+
serializer function. The return value of each serializer is passed as input
3165+
to the next serializer. Once all serializers have run, the resulting value
3166+
is coerced to a string. **Default:** If no serializers are provided, the
3167+
test runner's default serializers are used.
3168+
3169+
This function implements assertions for snapshot testing.
3170+
3171+
```js
3172+
test('snapshot test with default serialization', (t) => {
3173+
t.assert.snapshot({ value1: 1, value2: 2 });
3174+
});
3175+
3176+
test('snapshot test with custom serialization', (t) => {
3177+
t.assert.snapshot({ value3: 3, value4: 4 }, {
3178+
serializers: [(value) => JSON.stringify(value)]
3179+
});
3180+
});
3181+
```
3182+
30453183
### `context.diagnostic(message)`
30463184

30473185
<!-- YAML
@@ -3320,13 +3458,15 @@ Can be used to abort test subtasks when the test has been aborted.
33203458
[TAP]: https://testanything.org/
33213459
[TTY]: tty.md
33223460
[`--experimental-test-coverage`]: cli.md#--experimental-test-coverage
3461+
[`--experimental-test-snapshots`]: cli.md#--experimental-test-snapshots
33233462
[`--import`]: cli.md#--importmodule
33243463
[`--test-concurrency`]: cli.md#--test-concurrency
33253464
[`--test-name-pattern`]: cli.md#--test-name-pattern
33263465
[`--test-only`]: cli.md#--test-only
33273466
[`--test-reporter-destination`]: cli.md#--test-reporter-destination
33283467
[`--test-reporter`]: cli.md#--test-reporter
33293468
[`--test-skip-pattern`]: cli.md#--test-skip-pattern
3469+
[`--test-update-snapshots`]: cli.md#--test-update-snapshots
33303470
[`--test`]: cli.md#--test
33313471
[`MockFunctionContext`]: #class-mockfunctioncontext
33323472
[`MockTimers`]: #class-mocktimers

doc/node.1

+6
Original file line numberDiff line numberDiff line change
@@ -188,6 +188,9 @@ Enable code coverage in the test runner.
188188
.It Fl -experimental-test-module-mocks
189189
Enable module mocking in the test runner.
190190
.
191+
.It Fl -experimental-test-snapshots
192+
Enable snapshot testing in the test runner.
193+
.
191194
.It Fl -experimental-eventsource
192195
Enable experimental support for the EventSource Web API.
193196
.
@@ -451,6 +454,9 @@ whose name matches the provided pattern.
451454
.It Fl -test-timeout
452455
A number of milliseconds the test execution will fail after.
453456
.
457+
.It Fl -test-update-snapshots
458+
Regenerates the snapshot file used by the test runner for snapshot testing.
459+
.
454460
.It Fl -throw-deprecation
455461
Throw errors for deprecations.
456462
.

lib/internal/test_runner/harness.js

+1
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,7 @@ function setup(root) {
210210
counters: null,
211211
shouldColorizeTestFiles: false,
212212
teardown: exitHandler,
213+
snapshotManager: null,
213214
};
214215
root.harness.resetCounters();
215216
root.startTime = hrtime();

0 commit comments

Comments
 (0)