/snap

Snapshot testing for Go

Primary LanguageGoMIT LicenseMIT

Snap

Go Reference

Minimalistic snapshot testing for Go.

Similar to the concept of golden files, but instead of a separate file that contains the snapshot, the snapshot is directly in the source code tests.

Video

Highlights:

  • Simple, minimal API.
  • Provides automatic updating of the shapshot in code. Can trigger via environment variable SNAP_UPDATE=1 to update all snapshots at once, or can update one test at a time using the Update method.
  • Leverages the powerful go-cmp package for displaying rich diffs when the snapshot differs from what is expected.
  • Ability to ignore part of the input text by using a special <snap:ignore> marker.

Limitations:

  • When updating a snapshot that uses the <snap:ignore> marker, the marker is overwritten. This can be worked around by undoing that specific line back to the ignore marker(I do this easily with Git hunks), but it is indeed a little annoying to deal with.
  • Updating the snapshot does not currently work if the snap.Snap function is assigned to a different variable. Such as check := snap.Snap.

Inspired by:

Usage

func TestExample(t *testing.T) {
    checkAddition := func(x int, y int, want *snap.Snapshot) {
        got := x + y
        want.Diff(strconv.Itoa(got))
    }

    checkAddition(2, 2, snap.Snap(t, "8")) // should be 4
}

Running that test will fail, and prints the diff between the actual result (4) from the checkAddtion function, and what is specified in the snapshot:

=== RUN   TestExample
    snap_test.go:149: snap: Snapshot differs: (-want +got):
          string(
        -       "8",
        +       "4",
          )
    snap_test.go:149: snap: Rerun with SNAP_UPDATE=1 environmental variable to update the snapshot.
--- FAIL: TestExample (0.00s)

To update that snapshot automatically without manually editing the code, rerun the test with SNAP_UPDATE=1 and it will change snap.Snap("8") to snap.Snap("4") for you.

This is a small example, this testing strategy really speeds things up when you have large outputs that need changing, such as large JSON blobs or any substantial amount of text.

Ignoring data

Sometimes you have data in tests that change on each run. Such as timestamps, or random value. These values can be ignored using the special marker <snap:ignore>.

This example shows how to ignore a JSON field. The timestamp field in the person struct will be ignored when diffing the expected and got data.

func TestSnapJSONWithIgnore(t *testing.T) {
    checkJSON := func(want *snap.Snapshot) {
        type person struct {
            Name string    `json:"name"`
            Age  uint      `json:"age"`
            Time time.Time `json:"timestamp"`
        }

        p := person{
            Name: "Doug",
            Age:  20,
            Time: time.Now(),
        }

        want.DiffJSON(&p, "  ")
    }

    checkJSON(
        snap.Snap(t, `{
  "name": "Doug",
  "age": 20,
  "timestamp": "<snap:ignore>"
}`))
}

Import alias

Snapshot updating still works if you decide to import this package under a different alias, such as:

import (
    "strconv"
    "testing"
    foo "github.com/KasonBraley/snap"
)

func TestExample(t *testing.T) {
    checkAddition := func(x int, y int, want *snap.Snapshot) {
        got := x + y
        want.Diff(strconv.Itoa(got))
    }

    checkAddition(2, 2, foo.Snap(t, "8")) // "foo" instead of "snap" still works when using SNAP_UPDATE=1
}

Examples

The ./examples directory showcases some more elaborate use cases for this package, such as testing a CLI application.

The tests for this package might also serve as a good reference.