On this page
Snapshot testing
Snapshot testing is a testing technique that captures the output of your code and compares it against a stored reference version. Rather than manually writing assertions for each property, you let the test runner record the entire output structure, making it easier to detect any unexpected changes.
The Deno Standard Library has a snapshot module, which enables developers to write tests which assert a value against a reference snapshot. This reference snapshot is a serialized representation of the original value and is stored alongside the test file.
Basic usage Jump to heading
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.
To create an initial snapshot (or to update an existing snapshot), use the
-- --update
flag with the deno test
command.
Basic snapshot example Jump to heading
The below example shows how to use the snapshot library with the Deno.test
API. We can test a snapshot of a basic object, containing string and number
properties.
The assertSnapshot(t, a)
function compares the object against a stored
snapshot. The t
parameter is the test context that Deno provides, which the
snapshot function uses to determine the test name and location for storing
snapshots.
import { assertSnapshot } from "jsr:@std/testing/snapshot";
Deno.test("isSnapshotMatch", async (t) => {
const a = {
hello: "world!",
example: 123,
};
await assertSnapshot(t, a);
});
You will need to grant read and write file permissions in order for Deno to
write a snapshot file and then read it to test the assertion. If it is the first
time you are running the test a do not already have a snapshot, add the
--update
flag:
deno test --allow-read --allow-write -- --update
If you already have a snapshot file, you can run the test with:
deno test --allow-read
The test will compare the current output of the object against the stored snapshot. If they match, the test passes; if they differ, the test fails.
The snapshot file will look like this:
export const snapshot = {};
snapshot[`isSnapshotMatch 1`] = `
{
example: 123,
hello: "world!",
}
`;
You can edit your test to change the hello
string to "everyone!"
and run the
test again with deno test --allow-read
. This time the assertSnapshot
function will throw an AssertionError
, causing the test to fail because the
snapshot created during the test does not match the one in the snapshot file.
Updating snapshots Jump to heading
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.
deno test --allow-read --allow-write -- --update
New snapshots will only be created when the --update
flag is present.
Permissions Jump to heading
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 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 desired.
Version Control Jump to heading
Snapshot testing works best when changes to snapshot files are committed 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.
Options Jump to heading
The assertSnapshot
function can be called with an options
object which
offers greater flexibility and enables some non standard use cases:
import { assertSnapshot } from "jsr:@std/testing/snapshot";
Deno.test("isSnapshotMatch", async (t) => {
const a = {
hello: "world!",
example: 123,
};
await assertSnapshot(t, a, {/*custom options go here*/});
});
serializer Jump to heading
When you run a test with assertSnapshot
, the data you're testing needs to be
converted to a string format that can be written to the snapshot file (when
creating or updating snapshots) and compared with the existing snapshot (when
validating), this is called serialization.
The serializer
option allows you to provide a custom serializer function. This
custom function will be called by assertSnapshot
and be passed the value being
asserted. Your custom function must:
- Return a
string
- Be deterministic, (it will always produce the same output, given the same input).
The code below shows a practical example of creating and using a custom
serializer function for snapshot testing. This serialiser removes any ANSI
colour codes from a string using the
stripColour
string formatter from the
Deno Standard Library.
import { assertSnapshot, serialize } from "jsr:@std/testing/snapshot";
import { stripColor } from "jsr:@std/fmt/colors";
/**
* Serializes `actual` and removes ANSI escape codes.
*/
function customSerializer(actual: string) {
return serialize(stripColor(actual));
}
Deno.test("Custom Serializer", async (t) => {
const output = "\x1b[34mHello World!\x1b[39m";
await assertSnapshot(t, output, {
serializer: customSerializer,
});
});
snapshot = {};
snapshot[`Custom Serializer 1`] = `"Hello World!"`;
Custom serializers can be useful in a variety of scenarios:
- To remove irrelevant formatting (like ANSI codes shown above) and improve legibility
- To handle non-deterministic data. Timestamps, UUIDs, or random values can be replaced with placeholders
- To mask or remove sensitive data that shouldn't be saved in snapshots
- Custom formatting to present complex objects in a domain-specific format
Serialization with Deno.customInspect
Jump to heading
Because the default serializer uses Deno.inspect
under the hood, you can set
the property Symbol.for("Deno.customInspect")
to a custom serialization
function if desired:
// example_test.ts
import { assertSnapshot } from "jsr:@std/testing/snapshot";
class HTMLTag {
constructor(
public name: string,
public children: Array<HTMLTag | string> = [],
) {}
public render(depth: number) {
const indent = " ".repeat(depth);
let output = `${indent}<${this.name}>\n`;
for (const child of this.children) {
if (child instanceof HTMLTag) {
output += `${child.render(depth + 1)}\n`;
} else {
output += `${indent} ${child}\n`;
}
}
output += `${indent}</${this.name}>`;
return output;
}
public [Symbol.for("Deno.customInspect")]() {
return this.render(0);
}
}
Deno.test("Page HTML Tree", async (t) => {
const page = new HTMLTag("html", [
new HTMLTag("head", [
new HTMLTag("title", [
"Simple SSR Example",
]),
]),
new HTMLTag("body", [
new HTMLTag("h1", [
"Simple SSR Example",
]),
new HTMLTag("p", [
"This is an example of how Deno.customInspect could be used to snapshot an intermediate SSR representation",
]),
]),
]);
await assertSnapshot(t, page);
});
This test will produce the following snapshot.
export const snapshot = {};
snapshot[`Page HTML Tree 1`] = `
<html>
<head>
<title>
Simple SSR Example
</title>
</head>
<body>
<h1>
Simple SSR Example
</h1>
<p>
This is an example of how Deno.customInspect could be used to snapshot an intermediate SSR representation
</p>
</body>
</html>
`;