@jsenv/snapshot
Version:
Snapshot testing
233 lines (169 loc) ⢠7.07 kB
Markdown
//img.shields.io/npm/v/@jsenv/snapshot.svg?logo=npm&label=package)](https://www.npmjs.com/package/@jsenv/snapshot)
A powerful snapshot testing tool for JavaScript applications.
šø Create readable, reliable test snapshots
š Document code behavior with markdown
š Track changes in code output over time
š§© Normalize fluctuating values for stable comparisons
- [@jsenv/snapshot](
- [Table of Contents](
- [Introduction to Snapshot Testing](
- [Installation](
- [How `@jsenv/snapshot` Works](
- [API Reference](
- [snapshotTests(testFileUrl, fnRegisteringTests, options)](
- [when radius is 10](
- [when radius is null](
- [Why Use snapshotTests?](
- [takeFileSnapshot(fileUrl)](
- [takeDirectorySnapshot(directoryUrl)](
- [Stable Snapshots Across Environments](
- [Advanced Examples](
- [Compatibility](
- [Contributing](
Snapshot testing is a technique that:
1. Captures the output of code execution into files (snapshots)
2. Validates future code changes by:
- Reading the existing snapshot
- Executing the code
- Generating a new snapshot
- Comparing the two snapshots and reporting differences
This approach ensures your code continues to behave as expected by verifying its outputs remain consistent over time.
## Installation
```console
npm install --save-dev @jsenv/snapshot
```
## How `@jsenv/snapshot` Works
When running tests:
- **First run**: If no snapshot exists, one will be generated without comparison
- **Subsequent runs**: Snapshots are compared with the following behavior:
- In CI environments (`process.env.CI` is set): An error is thrown if differences are detected
- Locally: No error is thrown, allowing you to review changes with tools like git diff
> **Note**: All functions accept a `throwWhenDiff` parameter to force errors even in local environments.
## API Reference
### snapshotTests(testFileUrl, fnRegisteringTests, options)
The most powerful feature of this library - creates readable markdown snapshots of test executions.
```js
import { snapshotTests } from "@jsenv/snapshot";
const getCircleArea = (circleRadius) => {
if (isNaN(circleRadius)) {
throw new TypeError(
`circleRadius must be a number, received ${circleRadius}`,
);
}
return circleRadius * circleRadius * Math.PI;
};
await snapshotTests(import.meta.url, ({ test }) => {
test("when radius is 2", () => {
return getCircleArea(2);
});
test("when radius is 10", () => {
return getCircleArea(10);
});
test("when radius is null", () => {
try {
return getCircleArea(null);
} catch (e) {
return e;
}
});
});
```
This generates a markdown file documenting how your code behaves in different scenarios:
````markdown
```js
getCircleArea(2);
```
````
Returns:
```js
12.566370614359172;
```
```js
getCircleArea(10);
```
Returns:
```js
314.1592653589793;
```
```js
getCircleArea(null);
```
Throws:
```
TypeError: circleRadius must be a number, received null
```
````
See a full example at [./docs/\_circle_area.test.js/circle_area.test.js.md](./docs/_circle_area.test.js/circle_area.test.js.md)
```js
await snapshotTests(import.meta.url, fnRegisteringTests, {
throwWhenDiff: true, // Force error on snapshot differences (default: false in local, true in CI)
formatValue: (value) => {}, // Custom formatter for values
trackingConfig: {}, // Track specific resources like network requests or file operations
});
````
- **Assertion-free testing**: Simply call your functions and let the snapshots document their behavior
- **Self-documenting tests**: Markdown files serve as both test validation and documentation
- **Visual change reviews**: Code changes are reflected in snapshots, making reviews easy
- **Side effect tracking**: Automatically captures and documents:
- Console logs ([example](./docs/_log.test.js/log.test.js.md))
- Filesystem operations ([example](./docs/_filesystem.test.js/filesystem.test.js.md))
- And more
### takeFileSnapshot(fileUrl)
Captures and compares the state of a specific file.
```js
import { writeFileSync } from "node:fs";
import { takeFileSnapshot } from "@jsenv/snapshot";
const fileTxtUrl = new URL("./file.txt", import.meta.url);
const writeFileTxt = (content) => {
writeFileSync(fileTxtUrl, content);
};
// take snapshot of "./file.txt"
const fileSnapshot = takeFileSnapshot(fileTxtUrl);
writeFileTxt("Hello world");
// compare the state of "./file.txt" with previous version
fileSnapshot.compare();
```
Captures and compares the state of an entire directory.
```js
import { writeFileSync } from "node:fs";
import { takeDirectorySnapshot } from "@jsenv/snapshot";
const directoryUrl = new URL("./dir/", import.meta.url);
const writeManyFiles = () => {
writeFileSync(new URL("./a.txt", directoryUrl), "a");
writeFileSync(new URL("./b.txt", directoryUrl), "b");
};
// take snapshot of "./dir/"
const directorySnapshot = takeDirectorySnapshot(directoryUrl);
writeManyFiles();
// compare the state of "./dir/" with previous version
directorySnapshot.compare();
```
To ensure snapshots remain consistent across different machines and CI environments, `@jsenv/snapshot` automatically normalizes fluctuating values:
| Fluctuating Value | Stabilized As |
| ----------------- | ----------------------------- |
| Time durations | `"Xs"` instead of `"2.34s"` |
| Filesystem paths | Platform-independent paths |
| Network ports | Removed from URLs |
| Random IDs | Consistent placeholder values |
| Stack traces | Simplified and normalized |
This ensures your snapshot tests remain stable regardless of when or where they run.
- Testing complex assertion behavior: [@jsenv/assert/tests/array.test.js.md](../assert/tests/_array.test.js/array.test.js.md)
- Testing server-side builds with browser execution: [@jsenv/core/tests/script_type_module_basic.test.mjs](../../../tests/build/basics/script_type_module_basic/_script_type_module_basic.test.mjs/script_type_module_basic.test.mjs.md)
- Node.js: 16.x and above
- Works with most JavaScript test runners
- Compatible with ESM and CommonJS modules
If you encounter unstable snapshots due to fluctuating values not being properly normalized, please open an issue or submit a pull request.
[![npm package](https: