dream_test/matchers/snapshot

Snapshot matchers for dream_test.

Snapshot testing compares a value against a stored “golden” file. On first run, the snapshot is created automatically. On subsequent runs, the value is compared against the stored snapshot—any difference is a failure.

Why Snapshot Testing?

Snapshot tests excel at detecting unintended changes in complex outputs:

Basic Usage

import dream_test/assertions/should.{should, match_snapshot, or_fail_with}

it("renders user profile", fn() {
  render_profile(user)
  |> should()
  |> match_snapshot("./test/snapshots/user_profile.snap")
  |> or_fail_with("Profile should match snapshot")
})

How It Works

ScenarioBehavior
Snapshot missingCreates it, test passes
Snapshot matchesTest passes
Snapshot differsTest fails with diff

Updating Snapshots

When you intentionally change output, update snapshots by deleting them:

# Update one snapshot
rm ./test/snapshots/user_profile.snap
gleam test

# Update all snapshots in a directory
rm ./test/snapshots/*.snap
gleam test

Or use the helper functions:

// In a setup script or before tests
let _ = snapshot.clear_snapshot("./test/snapshots/user_profile.snap")
let _ = snapshot.clear_snapshots_in_directory("./test/snapshots")

Snapshot File Organization

Recommended structure:

test/
├── snapshots/
│   ├── api/
│   │   ├── users_list.snap
│   │   └── user_detail.snap
│   └── components/
│       ├── header.snap
│       └── footer.snap
└── my_test.gleam

Use descriptive paths that mirror your test structure.

Testing Non-Strings

For complex data structures, use match_snapshot_inspect:

build_complex_result()
|> should()
|> match_snapshot_inspect("./test/snapshots/complex.snap")
|> or_fail_with("Result should match snapshot")

This uses Gleam’s string.inspect to serialize the value.

Values

pub fn clear_snapshot(
  snapshot_path: String,
) -> Result(Nil, String)

Delete a snapshot file to force regeneration.

Use this to programmatically clear a snapshot. The next test run will create a fresh snapshot with the current output.

Parameters

  • snapshot_path - Path to the snapshot file to delete

Returns

  • Ok(Nil) - Snapshot was deleted (or didn’t exist)
  • Error(String) - Human-readable error message

Examples

Clear Before Test

// In a test setup
let _ = snapshot.clear_snapshot("./test/snapshots/user.snap")
// Next test will create a fresh snapshot

Conditional Update

case env.get("UPDATE_SNAPSHOTS") {
  Ok("true") -> {
    let _ = snapshot.clear_snapshot("./test/snapshots/output.snap")
  }
  _ -> Nil
}

Idempotent Behavior

This function succeeds even if the file doesn’t exist:

// Both calls succeed
let _ = snapshot.clear_snapshot("./test/snapshots/new.snap")
let _ = snapshot.clear_snapshot("./test/snapshots/new.snap")
pub fn clear_snapshots_in_directory(
  directory: String,
) -> Result(Int, String)

Delete all .snap files in a directory.

Clears all snapshot files in the given directory (non-recursively). Useful for resetting all snapshots before a major refactor.

Parameters

  • directory - Path to the directory containing snapshots

Returns

  • Ok(Int) - Number of snapshot files deleted
  • Error(String) - Human-readable error message

Examples

Clear All Snapshots

case snapshot.clear_snapshots_in_directory("./test/snapshots") {
  Ok(0) -> io.println("No snapshots to clear")
  Ok(n) -> io.println("Cleared " <> int.to_string(n) <> " snapshots")
  Error(msg) -> io.println("Error: " <> msg)
}

Clear Subdirectory

// Clear only API snapshots
let _ = snapshot.clear_snapshots_in_directory("./test/snapshots/api")

Notes

  • Only deletes files with .snap extension
  • Does not recurse into subdirectories
  • Non-snapshot files are left untouched
pub fn match_snapshot(
  value_or_result: types.MatchResult(String),
  snapshot_path: String,
) -> types.MatchResult(String)

Assert that a string value matches the content of a snapshot file.

This is the primary snapshot matcher. Use it when you have string output (JSON, HTML, plain text, etc.) that you want to compare against a snapshot.

Behavior

  • Snapshot doesn’t exist: Creates it and the test passes
  • Snapshot exists and matches: Test passes
  • Snapshot exists but differs: Test fails with detailed diff

Parameters

  • value_or_result - The MatchResult(String) from the assertion chain
  • snapshot_path - Path to the snapshot file (will be created if missing)

Examples

Basic Usage

it("serializes user to JSON", fn() {
  user_to_json(sample_user)
  |> should()
  |> match_snapshot("./test/snapshots/user.json")
  |> or_fail_with("User JSON should match snapshot")
})

With Transformation

it("renders HTML correctly", fn() {
  render_page(data)
  |> string.trim()  // Normalize whitespace
  |> should()
  |> match_snapshot("./test/snapshots/page.html")
  |> or_fail_with("Page HTML should match snapshot")
})

Error Handling

it("handles parse errors gracefully", fn() {
  case parse(invalid_input) {
    Ok(_) -> fail("Should have failed")
    Error(msg) ->
      msg
      |> should()
      |> match_snapshot("./test/snapshots/parse_error.snap")
      |> or_fail_with("Error message should match snapshot")
  }
})

Updating the Snapshot

rm ./test/snapshots/user.json && gleam test
pub fn match_snapshot_inspect(
  value_or_result: types.MatchResult(value),
  snapshot_path: String,
) -> types.MatchResult(value)

Assert that any value matches a snapshot when serialized.

Use this when testing complex data structures that aren’t strings. The value is converted to a string using string.inspect, which produces Gleam’s debug representation.

When to Use This

  • Testing return values of functions (records, tuples, lists)
  • Comparing parsed AST structures
  • Verifying complex state after operations

Parameters

  • value_or_result - The MatchResult(value) from the assertion chain
  • snapshot_path - Path to the snapshot file

Examples

Testing a Record

it("parses config correctly", fn() {
  parse_config(raw_toml)
  |> should()
  |> match_snapshot_inspect("./test/snapshots/config.snap")
  |> or_fail_with("Parsed config should match snapshot")
})

Testing a List

it("filters users correctly", fn() {
  users
  |> list.filter(is_active)
  |> should()
  |> match_snapshot_inspect("./test/snapshots/active_users.snap")
  |> or_fail_with("Active users should match snapshot")
})

Snapshot Format

The snapshot will contain the Gleam debug representation:

User(name: "Alice", age: 30, active: True)
[User(name: "Alice", age: 30), User(name: "Bob", age: 25)]

Note on Stability

The string.inspect output may change between Gleam versions. If you need stable serialization, convert to JSON or another format and use match_snapshot instead.

Search Document