Skip to content
rusthacks.dev
Testing

Snapshot Tests in Rust With Insta and Review Workflow

Insta stores the expected output of a test in a file and shows a diff when it changes. Learn the review workflow with cargo insta, inline snapshots, redactions and when snapshots go wrong.

3 min read
Summarize in AI
Flat illustration for the post “Snapshot Tests in Rust With Insta and Review Workflow”, composed around testing.

Writing a 40-line expected JSON document by hand, then updating it each time a field is added, is a chore that tempts people to test less. Snapshot testing hands the chore to a tool: the test records the output once, and later runs compare. What makes it work is the review step. This post shows the workflow with insta, a redaction and the traps. It is the output-heavy case in the testing in Rust guide, and complements assertions.

Rust 1.90, insta 1, cargo-insta installed.

Setup

Cargo.tomlTOML
[dev-dependencies]
insta = { version = "1", features = ["json", "redactions"] }
Shell
cargo install cargo-insta

A first snapshot

rust
pub fn report(name: &str, items: &[(&str, u32)]) -> String {
    let mut out = format!("Report for {name}\n");
    for (item, qty) in items {
        out.push_str(&format!("  {item:<8} x{qty}\n"));
    }
    out
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn renders_a_report() {
        insta::assert_snapshot!(report("ana", &[("apple", 3), ("pear", 12)]));
    }
}

The first cargo test fails and writes src/snapshots/demo__tests__renders_a_report.snap.new. Run cargo insta review, which shows:

Text
-old
+Report for ana
+  apple    x3
+  pear     x12

Press a to accept: the file becomes demo__tests__renders_a_report.snap, to be committed. Rerun and the test passes. Change the format in report and the test fails with a diff of the changed lines.

Structured data

rust
#[derive(serde::Serialize)]
struct Order { id: u64, created_at: String, total: f64 }

#[test]
fn order_json() {
    let o = Order { id: 4821, created_at: "2026-11-03T10:00:00Z".into(), total: 19.5 };
    insta::assert_json_snapshot!(o, {
        ".id" => "[id]",
        ".created_at" => "[timestamp]",
    });
}

The stored snapshot contains "id": "[id]" and "created_at": "[timestamp]". A redaction selector replaces the value before comparing, so the snapshot does not change when the real id or time does. This is how you snapshot output that includes generated values.

Inline snapshots

rust
insta::assert_snapshot!("a-b", @"a-b");

With the @"..." form, the expectation sits in the source, and cargo insta review rewrites it in place. It is nicer for short results. Long inline snapshots clutter the test; switch to files.

CI behavior

In CI, you want a difference to fail and nothing to be written. Set INSTA_UPDATE=no, or use cargo insta test --check, which also fails on unreferenced snapshot files. Never commit .snap.new; add *.snap.new to .gitignore (git ignore for Rust projects).

Where snapshots go wrong

  1. Accepting without reading. The tool cannot know the new output is correct; you can.
  2. Huge snapshots. A 3000-line file gets approved on faith. Snapshot the interesting part.
  3. Non-determinism. HashMap iteration order, timestamps and random ids make snapshots flaky; redact or sort (see flaky tests).
  4. Testing implementation detail. If the snapshot includes debug formatting of private types, every refactor shows up as a diff.

Limits

Snapshots tell you that output changed, not that it is right. Pair with a few exact assertions on the facts that matter. Crate docs: insta.rs.

Taggedinsta
Share

Keep reading

Get new posts by email

We send one email when a new post is published. No spam.

Unsubscribe in one click. No spam, ever.