Skip to content
Riot Docs

Search is only available in production builds. Try building and previewing the site to test it out locally.

Install Riot GitHub

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 external-snapshot helpers are:

  • assert_text
  • assert_json
  • assert_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:value

Sometimes writing a file is overkill. For those cases use:

  • assert_inline_text
  • assert_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.

For non-fixture tests, approved snapshots live under:

.riot/snapshots/<package>/<suite>/<test>.expected

For 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

Approved snapshots are not silently rewritten during normal test execution.

When the approved artifact is missing or differs:

  1. the assertion writes a pending candidate
  2. the test fails
  3. 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.

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.

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_fixture

That gives you a clean mapping:

  • input fixture file
  • transformed or rendered output
  • approved snapshot artifact

without a custom harness.

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.