Every language
12 linguagens, copy-ready. One at a time with syntax highlighting, or all inline.
JSJavaScript
import { expect, it } from 'vitest';
function renderReport(rows) {
return ['Report', '======', ...rows.map(([name, qty]) => `${name}: ${qty}`)].join('\n');
}
it('renders the report', () => {
// first run records the snapshot; every run after compares against it
expect(renderReport([['bolts', 12], ['washers', 340]])).toMatchSnapshot();
});
it('renders inline', () => {
// empty on purpose — vitest writes the recorded value into this call:
expect(renderReport([['nuts', 7]])).toMatchInlineSnapshot();
});The danger flag is vitest -u / --update: it re-records every failing snapshot in the run, blessing whatever the code now prints as the new truth — run it locally on a diff you have read, never in CI. toMatchInlineSnapshot stores the recording inside the test file itself, so the recorded expectation rides through code review instead of hiding in a __snapshots__ directory.
TSTypeScript
import { expect, it } from 'vitest';
interface Row { name: string; qty: number }
function renderReport(rows: Row[]): string {
return ['Report', '======', ...rows.map((r) => `${r.name}: ${r.qty}`)].join('\n');
}
it('renders the report', () => {
expect(renderReport([{ name: 'bolts', qty: 12 }])).toMatchSnapshot();
});The snapshot records vitest's pretty-format serialization of the value — a Date field or a random id lands in the file verbatim and rots the snapshot on every run. expect.addSnapshotSerializer({ test: (v) => v instanceof Date, print: () => '<date>' }) tames the volatile field at the serialization boundary — scrub it there, once, instead of in every fixture.
GoGo
import (
"bytes"
"flag"
"fmt"
"os"
"path/filepath"
"strings"
"testing"
)
var update = flag.Bool("update", false, "rewrite .golden files")
func renderReport(rows [][2]any) string {
lines := []string{"Report", "======"}
for _, r := range rows {
lines = append(lines, fmt.Sprintf("%v: %v", r[0], r[1]))
}
return strings.Join(lines, "\n")
}
func TestRenderReport(t *testing.T) {
got := renderReport([][2]any{{"bolts", 12}, {"washers", 340}})
golden := filepath.Join("testdata", "report.golden")
if *update {
if err := os.WriteFile(golden, []byte(got), 0o644); err != nil {
t.Fatal(err)
}
}
want, err := os.ReadFile(golden)
if err != nil {
t.Fatalf("read %s: %v (first run? re-record with: go test ./... -update)", golden, err)
}
if !bytes.Equal([]byte(got), want) {
t.Errorf("report changed; if intended, re-record with: go test ./... -update")
}
}No stdlib snapshot API — the golden-file pattern is the whole idiom, and testdata/ is the conventional home. The trap: -update is the sanctioned workflow (`go test ./... -update`), so CI must pin its test command WITHOUT the flag — a stray -update in a workflow re-records every golden and the suite goes green on top of broken output.
RsRust
use insta::assert_snapshot;
fn render_report(rows: &[(&str, u32)]) -> String {
let mut out = String::from("Report\n======");
for (name, qty) in rows {
out.push_str(&format!("\n{name}: {qty}"));
}
out
}
#[test]
fn renders_report() {
assert_snapshot!(render_report(&[("bolts", 12), ("washers", 340)]));
}A failing run writes renders_report.snap.new beside the committed .snap and keeps failing until `cargo insta accept` — blessing is a separate explicit step, and the pending .snap.new file surfacing in git status is what forces the new truth through review. Settings.bind with filters redacts volatile substrings (timestamps, ids) before anything is recorded.
PHPPHP
use PHPUnit\Framework\TestCase;
use Spatie\Snapshots\MatchesSnapshots;
final class RenderReportTest extends TestCase
{
use MatchesSnapshots;
public function testRendersReport(): void
{
$this->assertMatchesSnapshot(
$this->renderReport([['bolts', 12], ['washers', 340]])
);
}
private function renderReport(array $rows): string
{
$lines = ['Report', '======'];
foreach ($rows as [$name, $qty]) {
$lines[] = "{$name}: {$qty}";
}
return implode("\n", $lines);
}
}Re-recording is `vendor/bin/phpunit -d update-snapshots` — spatie's own flag, not a PHPUnit core one; per-vendor flag names differ across the PHP snapshot packages, so pin the exact command in CONTRIBUTING and keep it out of CI. Snapshots land in a __snapshots__/ directory next to the test — that directory's diff in the PR is the approval.
PyPython
import os
from pathlib import Path
SNAPSHOTS = Path(__file__).parent / 'snapshots'
def render_report(rows):
return '\n'.join(['Report', '======'] + [f'{n}: {q}' for n, q in rows])
def test_renders_report():
got = render_report([('bolts', 12), ('washers', 340)])
golden = SNAPSHOTS / 'report.golden'
if not golden.exists() or os.environ.get('UPDATE_SNAPSHOTS'):
golden.write_text(got) # re-record — then review the golden's git diff
assert got == golden.read_text()pytest-regressions packages this exact idiom (its file_regression.check() diffs written output against a committed file). Approving a changed golden is a code review of the .golden diff — nothing in the tooling can tell you whether the new output is a feature or a bug, which is why UPDATE_SNAPSHOTS never runs in CI.
C#C#
using System.Text;
using VerifyXunit;
using Xunit;
[UsesVerify]
public class RenderReportTests
{
static string RenderReport(params (string Name, int Qty)[] rows)
{
var b = new StringBuilder("Report\n======");
foreach (var (name, qty) in rows)
b.Append($"\n{name}: {qty}");
return b.ToString();
}
[Fact]
public Task RendersReport() =>
Verifier.Verify(RenderReport(("bolts", 12), ("washers", 340)));
}First run fails and writes RenderReportTests.RendersReport.received.txt; accepting — via the diff tool Verify launches, or Verify.DiffPilot — moves it to .verified.txt, which you commit. Verify auto-detects CI and refuses to write or accept there (tests just fail), and the auto-accept escape hatches (VerifierSettings.OnFirstVerify, AutoVerify) are forbidden by convention: blessing belongs to a human reading the diff.
JvJava
import org.approvaltests.Approvals;
import org.junit.jupiter.api.Test;
import java.util.List;
class RenderReportTest {
static String renderReport(List<Object[]> rows) {
StringBuilder b = new StringBuilder("Report\n======");
for (Object[] r : rows) b.append("\n").append(r[0]).append(": ").append(r[1]);
return b.toString();
}
@Test
void rendersReport() {
Approvals.verify(renderReport(List.of(
new Object[]{"bolts", 12},
new Object[]{"washers", 340})));
}
}Approvals.verify writes RenderReportTest.rendersReport.received.txt and fails until a matching .approved.txt exists — the received/approved pair is the review artifact: the diff tool shows exactly what changed, and approving is copying received over approved (the IDE plugin binds it to a key). Format with \n, never %n — the platform line separator makes the snapshot itself differ between a Windows laptop and Linux CI.
SwSwift
import Foundation
import XCTest
func renderReport(_ rows: [(name: String, qty: Int)]) -> String {
(["Report", "======"] + rows.map { "\($0.name): \($0.qty)" })
.joined(separator: "\n")
}
final class RenderReportTests: XCTestCase {
func testRendersReport() throws {
let got = renderReport([("bolts", 12), ("washers", 340)])
let goldenURL = Bundle(for: RenderReportTests.self)
.url(forResource: "report", withExtension: "golden")
let want = try String(contentsOf: XCTUnwrap(goldenURL), encoding: .utf8)
XCTAssertEqual(got, want) // re-record = edit the bundled golden by hand
}
}Neither XCTest nor Swift Testing (the 2024 successor framework) has a built-in snapshot matcher — the stdlib answer is a golden file added to the test target's bundle and compared with XCTAssertEqual, and re-recording means editing that resource by hand, which keeps blessing deliberate. pointfree's SnapshotTesting library (assertSnapshot(of:as:)) is the ecosystem heavyweight once you outgrow plain strings.
KtKotlin
import org.approvaltests.Approvals
import org.junit.jupiter.api.Test
class RenderReportTest {
private fun renderReport(vararg rows: Pair<String, Int>) = buildString {
append("Report\n======")
rows.forEach { (name, qty) -> append("\n$name: $qty") }
}
@Test
fun rendersReport() {
Approvals.verify(renderReport("bolts" to 12, "washers" to 340))
}
}Same ApprovalTests as Java — the received/approved pair and the diff-driven review carry over unchanged, because it is a JVM library rather than a language one. Kotest, the usual Kotlin-first alternative, has no first-class snapshot matcher, so ApprovalTests is the honest answer from Kotlin too.
RbRuby
require 'fileutils'
require 'minitest/autorun'
def render_report(rows)
(['Report', '======'] + rows.map { |name, qty| "#{name}: #{qty}" }).join("\n")
end
class TestRenderReport < Minitest::Test
GOLDEN = File.join(__dir__, 'snapshots', 'report.golden')
def test_renders_report
got = render_report([['bolts', 12], ['washers', 340]])
if ENV['UPDATE_SNAPSHOTS'] || !File.exist?(GOLDEN)
FileUtils.mkdir_p(File.dirname(GOLDEN))
File.write(GOLDEN, got) # re-record — the golden's diff is the review
end
assert_equal(File.read(GOLDEN), got)
end
endThe ecosystem gap vs JS: rspec-snapshot is thin and minitest has nothing built in, so hand-rolled golden files — write once, compare ever after — are the honest Ruby idiom. Re-record with UPDATE_SNAPSHOTS=1 and let the git diff on snapshots/report.golden be the review; the env var never reaches CI.
ZigZig
const std = @import("std");
const Row = struct { name: []const u8, qty: u32 };
fn renderReport(allocator: std.mem.Allocator, rows: []const Row) ![]u8 {
var buf: [4096]u8 = undefined;
var fbs = std.io.fixedBufferStream(&buf);
const w = fbs.writer();
try w.writeAll("Report\n======");
for (rows) |r| try w.print("\n{s}: {d}", .{ r.name, r.qty });
return allocator.dupe(u8, fbs.getWritten());
}
test "renderReport matches golden" {
const alloc = std.testing.allocator;
const got = try renderReport(alloc, &.{
.{ .name = "bolts", .qty = 12 },
.{ .name = "washers", .qty = 340 },
});
defer alloc.free(got);
// regenerate: ZIG_UPDATE_SNAPSHOTS=1 zig build test
if (std.posix.getenv("ZIG_UPDATE_SNAPSHOTS")) |_| {
try std.fs.cwd().makePath("testdata");
try std.fs.cwd().writeFile(.{
.sub_path = "testdata/report.golden",
.data = got,
});
return;
}
const golden = try std.fs.cwd().readFileAlloc(
alloc, "testdata/report.golden", 1 << 20);
defer alloc.free(golden);
try std.testing.expectEqualStrings(golden, got);
}No snapshot library exists, so the golden file read through std.fs.cwd — synchronous, allocation-explicit, comptime-friendly — is the whole pattern. Regeneration rides an env var: ZIG_UPDATE_SNAPSHOTS=1 zig build test. The trap is the same as every language here: the env var is one keystroke away from a CI config, and CI must never set it.