Std.Test.Snapshot
Std.Test.Snapshot exists for tests where the output itself is the contract.
That includes things like:
- pretty-printer output
- generated code
- rendered docs fragments
- parser round-trip surfaces
- protocol encodings where the shape matters more than one or two fields
It does not exist so every test can avoid writing assertions.
The core API
Section titled “The core API”The external-snapshot helpers are:
assert_textassert_jsonassert_with
Examples:
open Std
let tests = Test.[ case "rendered text stays stable" (fun ctx -> let actual = render () in Test.Snapshot.assert_text ~ctx ~actual);
case "json output stays stable" (fun ctx -> let actual = encode () in Test.Snapshot.assert_json ~ctx ~actual);]Use assert_with when you have some domain value and want a custom rendering function before
snapshotting:
Test.Snapshot.assert_with ~ctx ~render:my_renderer ~actual:valueInline snapshots
Section titled “Inline snapshots”Sometimes writing a file is overkill. For those cases use:
assert_inline_textassert_inline_json
Example:
open Std
Test.case "inline text snapshot" (fun ctx -> let actual = render_small_output () in Test.Snapshot.assert_inline_text ~ctx ~actual ~expected:"hello\n")Inline snapshots are best when:
- the expected artifact is short
- the value is central to understanding the test
- a separate file would add more noise than value
External snapshots are best when the artifact is larger and should be reviewed independently.
Where approved snapshots live
Section titled “Where approved snapshots live”For non-fixture tests, approved snapshots live under:
.riot/snapshots/<package>/<suite>/<test>.expectedFor fixture-backed tests, approved snapshots live next to the fixture input, usually by replacing the
input extension with .expected.
That difference is important:
- non-fixture suites keep snapshots in a centralized Riot-owned location
- fixture-driven suites keep the approved output adjacent to the corresponding input artifact
What happens on mismatch
Section titled “What happens on mismatch”Approved snapshots are not silently rewritten during normal test execution.
When the approved artifact is missing or differs:
- the assertion writes a pending candidate
- the test fails
- the candidate is written as
*.expected.new
That is a good workflow because it turns snapshot updates into an explicit review step rather than a silent mutation.
JSON snapshots
Section titled “JSON snapshots”assert_json and assert_inline_json canonicalize object-key order and render through
Std.Data.Json.to_string_pretty.
That is important because JSON snapshots are only useful if they stay reviewable.
Pretty, stable JSON makes review much cheaper than one-line serialized blobs.
Snapshots plus fixtures
Section titled “Snapshots plus fixtures”Std.Test.FixtureRunner is where snapshots get especially powerful.
Example shape:
open Std
let run_fixture ({ Test.FixtureRunner.test; fixture_path; _ } : Test.FixtureRunner.ctx) = let source = Fs.read fixture_path |> Result.expect ~msg:"fixture should read" in let actual = transform source in Test.Snapshot.assert_text ~ctx:test ~actual
let tests = Test.FixtureRunner.cases () ~dir:(Path.v "packages/my-package/tests/fixtures") ~run:run_fixtureThat gives you a clean mapping:
- input fixture file
- transformed or rendered output
- approved snapshot artifact
without a custom harness.
When to avoid snapshots
Section titled “When to avoid snapshots”Do not use snapshots when:
- a direct assertion is shorter and clearer
- the output is unstable for irrelevant reasons
- reviewers would struggle to tell whether a diff is meaningful
If a snapshot diff is mostly noise, the snapshot is not helping.