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:
- Rendered HTML/JSON/XML
- Error messages and logs
- Serialized data structures
- Any output where “expected” is hard to specify manually
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
| Scenario | Behavior |
|---|---|
| Snapshot missing | Creates it, test passes |
| Snapshot matches | Test passes |
| Snapshot differs | Test 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 deletedError(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
.snapextension - 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- TheMatchResult(String)from the assertion chainsnapshot_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- TheMatchResult(value)from the assertion chainsnapshot_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.