Cupaloy Snapshot Testing for Go

repository·master·Indexed 43 days ago

https://github.com/bradleyjkemp/cupaloy

A simple snapshot testing library for Go that captures test output and compares it against committed snapshot files to detect regressions. It uses go-spew for deep pretty-printing and supports custom configurations via the Configurator type, including custom snapshot directories, file extensions, and update triggers via the UPDATE_SNAPSHOTS environment variable.

Tokens
3.1K
Snippets
10
Records
16
Agent score
56%

What's inside Cupaloy

  1. Supported data formats and determinism

    master

    Cupaloy uses github.com/davecgh/go-spew to deep pretty-print test results via reflection.

    • Supported types: Most basic types including strings, slices, maps, and deeply nested structs.
    • Unsupported types: Functions and channels cannot be fully pretty-printed.
    • Requirement: Test output must be deterministic. If your output contains non-deterministic fields like timestamps, cupaloy will detect a change on every run and fail the test.
  2. Update snapshots using UPDATE_SNAPSHOTS

    master

    When your code's behavior changes intentionally, your snapshot tests will fail. To update the stored snapshot files to match the new output, run your tests with the UPDATE_SNAPSHOTS=true environment variable set.

    Note: This process will cause tests that were updated to fail (to prevent accidental updates in CI), but the snapshot files themselves will be updated on disk.

    UPDATE_SNAPSHOTS=true go test ./...
  3. Write a snapshot test with SnapshotT

    master

    To perform snapshot testing, pass your test output to cupaloy.SnapshotT(t, value).

    On the first run, cupaloy will automatically create a new snapshot file based on the test name using the go-spew package for deep pretty-printing. On subsequent runs, it compares the current value against the stored snapshot. If they differ, the test fails and provides a diff of the changes.

    func TestParsing(t *testing.T) {
        ast := ParseFile("test_input")
    
        // check that the result is the same as the last time the snapshot was updated
        // if the result has changed (e.g. because the behaviour of the parser has changed)
        // then the test will be failed with an error containing a diff of the changes
        cupaloy.SnapshotT(t, ast)
    }
  4. Understand Snapshot naming and limitations

    master

    Cupaloy uses the name of the calling function to automatically determine the snapshot filename. This leads to specific usage constraints:

    1. Automatic Naming: If you call Snapshot(...), the filename is derived from the function name.
    2. Single Call Constraint: Because the filename is tied to the function name, you can only call Snapshot (or SnapshotT) once per function. Calling it multiple times will attempt to use the same filename for different data, causing mismatches.
    3. Workarounds for Multiple Snapshots:
      • SnapshotMulti(snapshotID string, ...): Appends a unique ID to the function name (e.g., TestMyFunc-id1).
      • SnapshotWithName(snapshotName string, ...): Uses a completely custom name, ignoring the function name.
      • Sub-tests: Use t.Run() to create sub-tests, allowing you to call SnapshotT once in each sub-test.
  5. Troubleshoot Windows line ending issues

    master

    On Windows, if Git is configured to convert to during checkout, snapshot tests may fail even if the content appears identical. Ensure your Git configuration handles line endings correctly to prevent mismatches between the snapshot file and the test output.

  6. Use SnapshotT in table-driven tests

    master

    You can use cupaloy.SnapshotT within subtests in a table-driven test pattern. cupaloy will automatically resolve the correct snapshot file based on the subtest name provided by t.Run.

    var testCases = map[string][]string{
        "TestCaseOne": []string{......},
        "AnotherTestCase": []string{......},
        ....
    }
    
    func TestCases(t *testing.T) {
        for testName, args := range testCases {
            t.Run(testName, func(t *testing.T) {
                result := functionUnderTest(args...)
                cupaloy.SnapshotT(t, result)
            })
        }
    }
  7. Change the snapshot directory with New()

    master

    If you do not want to use the default snapshot location, you can create a custom snapshotter using cupaloy.New() and providing a cupaloy.SnapshotSubdirectory option. This allows you to specify a custom directory for your snapshot files.

    func TestSubdirectory(t *testing.T) {
        result := someFunction()
        snapshotter := cupaloy.New(cupaloy.SnapshotSubdirectory("testdata"))
        err := snapshotter.Snapshot(result)
        if err != nil {
            t.Fatalf("error: %s", err)
        }
    }
  8. Configure snapshot storage and file formats

    master

    Use these configurators to manage how and where snapshots are saved on disk:

    • SnapshotSubdirectory(name string): Sets the directory where snapshots are stored. Default is .snapshots.
    • SnapshotFileExtension(snapshotFileExtension string): Appends a specific extension to snapshot files (e.g., .html, .json). This is helpful for syntax highlighting in IDEs. Default is an empty string "".
    // Store snapshots in 'testdata' with '.html' extension
    cupaloy.New(
        cupaloy.SnapshotSubdirectory("testdata"),
        cupaloy.SnapshotFileExtension(".html"),
    )
  9. Customize snapshot update triggers

    master

    You can control when snapshots are updated (overwritten with new data) using two methods:

    1. EnvVariableName(name string): Sets a custom environment variable to trigger updates. By default, cupaloy checks for UPDATE_SNAPSHOTS.
    2. ShouldUpdate(f func() bool): Provides a custom function to determine if updates should occur. This is useful for linking updates to CLI flags.

    Default behavior: Checks for the presence of the UPDATE_SNAPSHOTS environment variable.

    // Trigger updates via a custom environment variable
    cupaloy.New(cupaloy.EnvVariableName("MY_CUSTOM_UPDATE_VAR"))
    
    // Trigger updates via a Go test flag
    var update = flag.Bool("update", false, "update snapshots")
    cupaloy.New(cupaloy.ShouldUpdate(func() bool {
        return *update
    }))
  10. Use the Global configuration instance

    master

    The cupaloy.Global variable is the default Config instance used by package-level functions like cupaloy.SnapshotT.

    If you want to reset your configuration to the factory defaults (the state before any global changes were applied), use cupaloy.NewDefaultConfig().

  11. How SnapshotT handles test failures

    master

    When using SnapshotT(t TestingT, i ...interface{}), the function interacts with the provided TestingT (typically *testing.T).

    • If the snapshot matches, the test continues normally.
    • If a mismatch is detected or a new snapshot is created, SnapshotT will call t.Fatal(err) or t.Error(err) depending on the fatalOnMismatch configuration setting.
    • It uses t.Helper() to ensure failure locations point to your test code rather than the cupaloy library internals.
    • It automatically replaces / with - in the test name to create a valid filename.
  12. Configure cupaloy using Configurator options

    master

    Cupaloy uses a functional options pattern via the Configurator type to customize snapshotting behavior. These options are passed to cupaloy.New() to create a new configured instance.

    Common tasks include:

    • Changing the environment variable used to trigger updates.
    • Customizing the snapshot directory.
    • Setting file extensions for snapshots.
    • Controlling error handling (e.g., whether to use t.Fatal or t.Error).
    // Example: Creating a custom cupaloy instance
    cupaloy.New(
        cupaloy.SnapshotSubdirectory("testdata"),
        cupaloy.SnapshotFileExtension(".html"),
        cupaloy.FailOnUpdate(false),
    )