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

feat(testing): Implement "assertSnapshot" #2039

Merged
merged 47 commits into from
Apr 20, 2022
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
47 commits
Select commit Hold shift + click to select a range
73467da
wip: Implement assertSnapshot
hyp3rflow Mar 17, 2022
4d8bf6c
wip: Get full ident from recursive getName function
hyp3rflow Mar 17, 2022
4833857
format
hyp3rflow Mar 18, 2022
31abdb8
Apply bcheidemann's review
hyp3rflow Mar 18, 2022
418cc43
proposal: Use `Deno.args` for update and `globalThis.onunload` instea…
hyp3rflow Mar 22, 2022
58df39e
Add count
hyp3rflow Apr 6, 2022
f191dd3
jest snapshot style
hyp3rflow Apr 6, 2022
9acd82c
add test case
hyp3rflow Apr 6, 2022
ea2479f
refactor: apply review comments 1
bcheidemann Apr 15, 2022
2b717b6
refactor: remove duplicate format calls
bcheidemann Apr 15, 2022
46b0b9a
feat: show number of snapshots updated
bcheidemann Apr 15, 2022
a199814
fix: only call writeSnapshotFileSync once per test
bcheidemann Apr 15, 2022
d36765c
refactor: format files
bcheidemann Apr 15, 2022
9df0184
fix: output snapshots to __snapshots__ directory
bcheidemann Apr 15, 2022
2538887
fix: handle missing snapshot file
bcheidemann Apr 15, 2022
c710140
refactor: group variables into assert snapshot context
bcheidemann Apr 15, 2022
c53bb73
refactor: use maps
bcheidemann Apr 15, 2022
140bd96
refactor: tidyup
bcheidemann Apr 15, 2022
b6d55e3
docs: add documentation for assertSnapshot
bcheidemann Apr 15, 2022
d024982
refactor: move code to be adjacent to other asserts
bcheidemann Apr 15, 2022
da8e582
fix: single line snapshots captured correctly
bcheidemann Apr 15, 2022
03b250e
fix: escape characters correctly
bcheidemann Apr 15, 2022
f4b4174
fix: type fix for assertSnapshot example
bcheidemann Apr 15, 2022
063261b
Merge branch 'main' of github.com:denoland/deno_std into feat/assertS…
bcheidemann Apr 15, 2022
a4bf357
Merge branch 'feat/assertSnapshot' of github.com:hyp3rflow/deno_std i…
bcheidemann Apr 15, 2022
6ca3bd1
refactor: format files
bcheidemann Apr 15, 2022
5ec8e41
fix: snapshot path to match Jest
bcheidemann Apr 15, 2022
2f0cc55
fix: improve assertSnapshot output
bcheidemann Apr 15, 2022
9e4390c
docs: add better documentation for assertSnapshot
bcheidemann Apr 17, 2022
fe87cfa
Merge branch 'main' of github.com:denoland/deno_std into feat/assertS…
bcheidemann Apr 17, 2022
b1782f8
docs: add link to Snapshot Testing section
bcheidemann Apr 17, 2022
21b96d0
test: add test data to map
bcheidemann Apr 17, 2022
7ad5a8b
refactor: move assertSnapshot to snapshot.ts
bcheidemann Apr 17, 2022
666f32d
fix: snapshot import on windows
bcheidemann Apr 17, 2022
3902ec7
docs: use "t" instead of "test" for assertSnapshot example
bcheidemann Apr 19, 2022
e13af8a
docs: use "t" instead of "test" for assertSnapshot example
bcheidemann Apr 19, 2022
158b8d5
docs: fix typescript issues in docs
bcheidemann Apr 19, 2022
c3264dd
fix: write snapshot file to correct path
bcheidemann Apr 19, 2022
e1155a0
fix: add training new line to snapshot files
bcheidemann Apr 19, 2022
fddf34d
refactor: use addEventListener for snapshot teardown
bcheidemann Apr 19, 2022
24f1868
test: add tests for assertSnapshot update flag
bcheidemann Apr 19, 2022
9c34b02
refactor: fromat files
bcheidemann Apr 19, 2022
03d2a4c
docs: fix snapshot test examples
bcheidemann Apr 19, 2022
0a66471
docs: fix assertSnapshot example and
bcheidemann Apr 19, 2022
4542ea9
test(node): update global var leak detection
kt3k Apr 20, 2022
faeb691
fix: fix snapshot assertions in --update testing
kt3k Apr 20, 2022
26aba27
fix: mask more timing notations
kt3k Apr 20, 2022
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
1 change: 1 addition & 0 deletions node/_tools/test/common/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,7 @@ let knownGlobals = [
getParent,
window,
self,
reportError,
];

if (global.AbortController)
Expand Down
87 changes: 87 additions & 0 deletions testing/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ pretty-printed diff of failing assertion.
`expected` values.
- `assertObjectMatch()` - Make an assertion that `actual` object match
`expected` subset object
- `assertSnapshot()` - Make an assertion that `actual` matches a snapshot
- `assertThrows()` - Expects the passed `fn` to throw. If `fn` does not throw,
this function does. Also compares any errors thrown to an optional expected
`Error` class and checks that the error `.message` includes an optional
Expand Down Expand Up @@ -87,6 +88,22 @@ Deno.test("isNotStrictlyEqual", function (): void {
});
```

Using `assertSnapshot()`:

For more usage information, see [Snapshot Testing](#snapshot-testing).

```ts
import { assertSnapshot } from "https://deno.land/std@$STD_VERSION/testing/snapshot.ts";

Deno.test("isSnapshotMatch", async function (t): Promise<void> {
const a = {
hello: "world!",
example: 123,
};
await assertSnapshot(t, a);
});
```

Using `assertThrows()`:

```ts
Expand Down Expand Up @@ -154,6 +171,76 @@ Deno.test("fails", async function () {
});
```

## Snapshot Testing

### Basic usage:

The `assertSnapshot` function will create a snapshot of a value and compare it
to a reference snapshot, which is stored alongside the test file in the
`__snapshots__` directory.

```ts
// example_test.ts
import { assertSnapshot } from "https://deno.land/std@$STD_VERSION/testing/snapshot.ts";

Deno.test("isSnapshotMatch", async function (t): Promise<void> {
const a = {
hello: "world!",
example: 123,
};
await assertSnapshot(t, a);
});
```

```js
// __snapshots__/example_test.ts.snap
export const snapshot = {};

snapshot[`isSnapshotMatch 1`] = `
{
example: 123,
hello: "world!",
}
`;
```

Calling `assertSnapshot` in a test will throw an `AssertionError`, causing the
test to fail, if the snapshot created during the test does not match the one in
the snapshot file.

### Updating Snapshots:

When adding new snapshot assertions to your test suite, or when intentionally
making changes which cause your snapshots to fail, you can update your snapshots
by running the snapshot tests in update mode. Tests can be run in update mode by
passing the `--update` or `-u` flag as an argument when running the test. When
this flag is passed, then any snapshots which do not match will be updated.

```sh
deno test --allow-all -- --update
```

Additionally, new snapshots will only be created when this flag is present.

### Permissions:

When running snapshot tests, the `--allow-read` permission must be enabled, or
else any calls to `assertSnapshot` will fail due to insufficient permissions.
Additionally, when updating snapshots, the `--allow-write` permission must also
be enabled, as this is required in order to update snapshot files.

The `assertSnapshot` function will only attempt to read from and write to
snapshot files. As such, the allow list for `--allow-read` and `--allow-write`
can be limited to only include existing snapshot files, if so desired.

### Version Control:

Snapshot testing works best when changes to snapshot files are comitted
alongside other code changes. This allows for changes to reference snapshots to
be reviewed along side the code changes that caused them, and ensures that when
others pull your changes, their tests will pass without needing to update
snapshots locally.

Copy link
Member

Choose a reason for hiding this comment

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

Great docs, could you also open a PR to https://github.com/denoland/manual that adds "Snapshot testing" page to "Testing" chapter?

Copy link
Contributor

Choose a reason for hiding this comment

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

## Benching

These APIs are deprecated. Use Deno.bench() instead. See
Expand Down
178 changes: 178 additions & 0 deletions testing/__snapshots__/snapshot_test.ts.snap
Original file line number Diff line number Diff line change
@@ -0,0 +1,178 @@
export const snapshot = {};

snapshot[`Snapshot Test 1`] = `
{
a: 1,
b: 2,
}
`;

snapshot[`Snapshot Test 2`] = `
TestClass {
a: 1,
b: 2,
}
`;

snapshot[`Snapshot Test 3`] = `
Map {
"Hello" => "World!",
1 => 2,
[Function] => "World!",
}
`;

snapshot[`Snapshot Test 4`] = `
Set {
1,
2,
3,
}
`;

snapshot[`Snapshot Test 5`] = `
{
fn: [Function: fn],
}
`;

snapshot[`Snapshot Test 6`] = `[Function: fn]`;

snapshot[`Snapshot Test 7`] = `
[
1,
2,
3,
]
`;

snapshot[`Snapshot Test 8`] = `"hello world"`;

snapshot[`Snapshot Test - step 1`] = `
{
a: 1,
b: 2,
}
`;

snapshot[`Snapshot Test - step > Nested 1`] = `
TestClass {
a: 1,
b: 2,
}
`;

snapshot[`Snapshot Test - step > Nested 2`] = `
Map {
"Hello" => "World!",
1 => 2,
[Function] => "World!",
}
`;

snapshot[`Snapshot Test - step > Nested > Nested Nested 1`] = `
Set {
1,
2,
3,
}
`;

snapshot[`Snapshot Test - step > Nested > Nested Nested 2`] = `
{
fn: [Function: fn],
}
`;

snapshot[`Snapshot Test - step > Nested > Nested Nested 3`] = `[Function: fn]`;

snapshot[`Snapshot Test - step > Nested 3`] = `
[
1,
2,
3,
]
`;

snapshot[`Snapshot Test - step 2`] = `"hello world"`;

snapshot[`Snapshot Test - Adverse String \\ \` \${} 1`] = `"\\\\ \` \${}"`;

snapshot[`Snapshot Test - Failed Assertion > Object 1`] = `
[
1,
2,
3,
]
`;

snapshot[`Snapshot Test - Failed Assertion > Object 2`] = `
[
"Snapshot does not match:",
"",
"",
" [Diff] Actual / Expected",
"",
"",
" [",
" 1,",
" 2,",
"+ 3,",
" ]",
"",
]
`;

snapshot[`Snapshot Test - Failed Assertion > String 1`] = `"Hello World!"`;

snapshot[`Snapshot Test - Failed Assertion > String 2`] = `
[
"Snapshot does not match:",
"",
"",
" [Diff] Actual / Expected",
"",
"",
'- "Hello!"',
'+ "Hello World!"',
"",
"",
]
`;

snapshot[`Snapshot Test - Update 1`] = `
[
"running 1 test from testing/.tmp/test.ts",
"Snapshot Test - Update ... ok (--ms)",
"",
" > 1 snapshots updated.",
"",
"test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (--ms)",
"",
"",
]
`;

snapshot[`Snapshot Test - Update 2`] = `
[
"running 1 test from testing/.tmp/test.ts",
"Snapshot Test - Update ... ok (--ms)",
"",
"test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (--ms)",
"",
"",
]
`;

snapshot[`Snapshot Test - Update 3`] = `
[
"running 1 test from testing/.tmp/test.ts",
"Snapshot Test - Update ... ok (--ms)",
"",
" > 1 snapshots updated.",
"",
"test result: ok. 1 passed; 0 failed; 0 ignored; 0 measured; 0 filtered out (--ms)",
"",
"",
]
`;
75 changes: 75 additions & 0 deletions testing/_diff.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,16 @@
// Copyright 2018-2022 the Deno authors. All rights reserved. MIT license.
// This module is browser compatible.

import {
bgGreen,
bgRed,
bold,
gray,
green,
red,
white,
} from "../fmt/colors.ts";

interface FarthestPoint {
y: number;
id: number;
Expand Down Expand Up @@ -358,3 +368,68 @@ export function diffstr(A: string, B: string) {

return diffResult;
}

/**
* Colors the output of assertion diffs
* @param diffType Difference type, either added or removed
*/
function createColor(
diffType: DiffType,
{ background = false } = {},
): (s: string) => string {
switch (diffType) {
case DiffType.added:
return (s: string): string =>
background ? bgGreen(white(s)) : green(bold(s));
case DiffType.removed:
return (s: string): string => background ? bgRed(white(s)) : red(bold(s));
default:
return white;
}
}

/**
* Prefixes `+` or `-` in diff output
* @param diffType Difference type, either added or removed
*/
function createSign(diffType: DiffType): string {
switch (diffType) {
case DiffType.added:
return "+ ";
case DiffType.removed:
return "- ";
default:
return " ";
}
}

export function buildMessage(
diffResult: ReadonlyArray<DiffResult<string>>,
{ stringDiff = false } = {},
): string[] {
const messages: string[] = [], diffMessages: string[] = [];
messages.push("");
messages.push("");
messages.push(
` ${gray(bold("[Diff]"))} ${red(bold("Actual"))} / ${
green(bold("Expected"))
}`,
);
messages.push("");
messages.push("");
diffResult.forEach((result: DiffResult<string>): void => {
const c = createColor(result.type);
const line = result.details?.map((detail) =>
detail.type !== DiffType.common
? createColor(detail.type, { background: true })(detail.value)
: detail.value
).join("") ?? result.value;
diffMessages.push(c(`${
createSign(result.type)
}${line}`));
});
messages.push(...(stringDiff ? [diffMessages.join("")] : diffMessages));
messages.push("");

return messages;
}
Loading