DOCS/Snapshot Testing

Snapshot Testing

Snapshot testing compares actual output against a stored reference file. This is useful for verifying that scan results, generated files, or command output haven't changed unexpectedly.

When to Use Snapshots

ScenarioApproach
Simple exit code checkexitCode: 0
Output contains a substringstdout.contains
Exact output matchSnapshot assertion
Complex multi-line outputSnapshot assertion
Generated file verificationFile + snapshot assertion

Use snapshots when the output is complex enough that inline assertions become unwieldy.

Defining Snapshot Assertions

Test definitions can include snapshot assertions. Here's a real example:

  "functional-examples": {
    "title": "Greeting Function",
    "test": [
      {
        "name": "scan output matches snapshot",
        "steps": [
          {
            "command": "bash ../../scan.sh",
            "assertions": {
              "exitCode": 0
            }
          },
          {
            "command": "true",
            "assertions": {
              "snapshot": {
                "path": "../../output.txt",
                "snapshot": "../../__snapshots__/scan-output.txt"
              }
            }
          }
        ]
      },
      {
        "name": "cleanup",
        "options": {
          "command": "rm -f ../../output.txt"
        },
        "assertions": {
          "exitCode": 0
        }
      }
    ]
  },

Or check multiple snapshots:

assertions:
  snapshots:
    - path: dist/index.js
      snapshot: __snapshots__/index.js
    - path: dist/types.d.ts
      snapshot: __snapshots__/types.d.ts

First-Run Behavior

On the first run, if the snapshot file doesn't exist, the test runner creates it from the actual output. Subsequent runs compare against the stored snapshot.

Updating Snapshots

When output intentionally changes, update snapshots with the -u flag:

npx functional-examples test -u

This overwrites stored snapshot files with current output.

Multi-Step Snapshot Workflow

  "functional-examples": {
    "title": "Greeting Function",
    "test": [
      {
        "name": "scan output matches snapshot",
        "steps": [
          {
            "command": "bash ../../scan.sh",
            "assertions": {
              "exitCode": 0
            }
          },
          {
            "command": "true",
            "assertions": {
              "snapshot": {
                "path": "../../output.txt",
                "snapshot": "../../__snapshots__/scan-output.txt"
              }
            }
          }
        ]
      },
      {
        "name": "cleanup",
        "options": {
          "command": "rm -f ../../output.txt"
        },
        "assertions": {
          "exitCode": 0
        }
      }
    ]
  },

This pattern:

  1. Runs the scan and captures output to a file
  2. Compares the file against a stored snapshot
  3. Cleans up temporary files

The scan script strips non-deterministic output (like timing) before saving:

#!/usr/bin/env bash
# Scan for examples and save output to a file for snapshot comparison

cd "$(dirname "$0")"

# Run the scan and capture output, stripping the timing line (non-deterministic)
npx functional-examples scan | sed '/^Scan completed in/d' > output.txt

And here's the stored snapshot it compares against:

Found 2 example(s)

ID                            Title                                   Files     Extractor
------------------------------------------------------------------------------------------
snapshot-greeting             Greeting Function                       1         javascript-extractor
greeting-example              Greeting Function                       2         javascript-extractor

Snapshots for Generated Content

Snapshots are especially powerful for verifying generated files. The documentation plugin example uses a snapshot to verify the markdown output produced by functional-examples documentation:

---
generated: true
---

# Sample for Documentation

A simple example demonstrating documentation generation

## `src/sample.ts`

### Region: `setup`

```typescript
import { readFileSync } from 'node:fs';

/**
 * Read and parse a configuration file.
 */
export function loadConfig(path: string): Record<string, unknown> {
  const content = readFileSync(path, 'utf-8');
  return JSON.parse(content);
}

Region: usage

// Load configuration from a JSON file
const config = loadConfig('config.json');
console.log('Loaded config:', config);

This ensures that changes to the documentation template or plugin logic are caught immediately by the test suite.

## Annotating Snapshots for Documentation

Snapshot files can include region tags so documentation can extract specific sections:

```text
#_region scan-result
Found 1 example(s)
...
#_endregion scan-result

Documentation can then reference: <%= example('snapshot-testing').region('scan-result') %>

Parser Pipeline Transparency

Region tags in snapshot files are processed by the parser pipeline — they're stripped before comparison. This means you can annotate snapshots with #_region tags for documentation without affecting test assertions.

Anti-Patterns

  • Don't use --update-snapshots in CI — CI should detect drift, not silently fix it
  • Don't snapshot non-deterministic output — timestamps, random IDs, and absolute paths make snapshots flaky
  • Don't use snapshots for simple checksstdout.contains is more resilient to formatting changes

See also: Test Plugin for the full assertion reference, CI Integration for running snapshot tests in pipelines.