go-snaps

repository·main·Indexed 42 days ago

https://github.com/gkampitakis/go-snaps

A Go package providing Jest-like snapshot testing capabilities. It allows developers to capture and compare structured data (JSON, YAML) or unstructured data against stored snapshots using functions like MatchSnapshot, MatchJSON, and MatchYAML. Features include property matchers for dynamic data, experimental inline snapshots, and tools for updating or cleaning obsolete snapshots via the UPDATE_SNAPS environment variable.

Tokens
3K
Snippets
12
Records
16
Agent score
4%

What's inside go-snaps

  1. Use Matchers in JSON and YAML snapshots

    main

    Matchers are functions used as the third argument in MatchJSON or MatchYAML to act as property matchers. They allow you to replace specific paths with placeholders or custom validation logic.

    Path Syntax:

    • JSON: Uses gjson syntax.
    • YAML: Uses github.com/goccy/go-yaml path syntax.

    Built-in Matchers:

    • match.Any(paths...): Replaces targeted paths with a placeholder (default <Any Value>).
      • .Placeholder(value): Sets a custom placeholder.
      • .ErrOnMissingPath(bool): If true (default), errors if the path is missing.
    • match.Type[T](paths...): Replaces targeted paths with <Type:T>.
      • .ErrOnMissingPath(bool): If true (default), errors if the path is missing.
    • match.Custom(path, func): Allows custom validation. The callback receives the value and should return (any, error). If it returns an error, the test fails.
      • .Placeholder(value): Sets a custom placeholder.
      • .ErrOnMissingPath(bool): If true (default), errors if the path is missing.
    // Example match.Any
    match.Any("user.name", "user.email")
    
    // Example match.Type
    match.Type[string]("user.info")
    
    // Example match.Custom
    match.Custom("user.age", func(val any) (any, error) {
        age, ok := val.(float64)
        if !ok {
            return nil, fmt.Errorf("expected number but got %T", val)
        }
        return "some number", nil
    })
  2. Skip tests with go-snaps

    main
    Standard Go t.Skip, t.Skipf, and t.SkipNow calls prevent go-snaps from accurately tracking whether a test was skipped or removed. To ensure go-snaps can correctly identify obsolete snapshots when tests are skipped, use the provided wrappers for t.Skip, t.Skipf, and t.SkipNow exposed by the library.
  3. Snapshot file structure

    main

    Snapshots are stored in a file using the following format:

    [TestName - Number]
    <data>
    ---

    TestID consists of the test name followed by an increasing number (e.g., [TestSimple/should_make_a_map_snapshot - 1]) to allow multiple snapshot assertions within a single test.

    Escaping: If your data contains the sequence --- at the start of a line followed by a newline, go-snaps escapes it as /-/-/-/ to prevent it from being interpreted as a termination character.

  4. Clean obsolete snapshots

    main

    To identify and manage obsolete (unused) snapshots, you must use TestMain(m *testing.M) to call snaps.Clean(m) after all tests have finished. This ensures go-snaps can accurately track which snapshots were not called during the test suite execution.

    • To identify obsolete snapshots: Call snaps.Clean(m). If running with the verbose flag (-v), a Snapshot Summary will be printed.
    • To automatically remove obsolete snapshot files: Set the UPDATE_SNAPS environment variable to clean.

    Note: snaps.Clean(m) returns a dirty boolean and an err. If dirty is true, it indicates some snapshots were outdated.

    func TestMain(m *testing.M) {
      v := m.Run()
    
      // After all tests have run `go-snaps` can check for unused snapshots
      snaps.Clean(m)
      // dirty, err := snaps.Clean(m)
      // _ = err
      // if dirty {
      //   fmt.Println("Some snapshots were outdated.")
      //   os.Exit(1)
      // }
    
      os.Exit(v)
    }
  5. Use MatchInlineSnapshot (Experimental)

    main

    The MatchInlineSnapshot feature allows you to store expected values directly in your test source code instead of external files.

    Workflow:

    1. First run: Pass nil as the last argument to create the snapshot.
    2. Automatic Update: Run your tests again, and go-snaps will automatically insert the snaps.Inline("...") call into your code.
    3. Subsequent runs: The test compares actual values against the inline code.

    Note: This feature is experimental.

    // 1. Initial run to generate
    func TestInlineSnapshot(t *testing.T) {
        snaps.MatchInlineSnapshot(t, "Hello World", nil)
    }
    
    // 2. After running, the code is updated to:
    func TestInlineSnapshot(t *testing.T) {
        snaps.MatchInlineSnapshot(t, "Hello World", snaps.Inline("Hello World"))
    }
  6. Update failing snapshots

    main

    To update failing snapshots with their new values, set the UPDATE_SNAPS environment variable to true when running your tests. You can use the standard Go -run flag to target specific tests if you do not want to update all of them at once.

    UPDATE_SNAPS=true go test ./...
  7. Running tests in CI

    main
    When go-snaps detects a CI environment (via ciinfo), it automatically fails tests if snapshots are missing or if there are diffs. This prevents accidental failures in CI due to uncommitted snapshots. To force snapshot creation or updates in CI, set UPDATE_SNAPS=always.
  8. Sort snapshots deterministically

    main

    By default, go-snaps appends new snapshots to the file, which can result in random ordering during parallel tests. To ensure snapshots are stored in a deterministic, sorted order, call snaps.Clean with the Sort option set to true within your TestMain function.

    func TestMain(m *testing.M) {
      v := m.Run()
    
      // After all tests have run `go-snaps` will sort snapshots
      snaps.Clean(m, snaps.CleanOpts{Sort: true})
    
      os.Exit(v)
    }
  9. Configure go-snaps via WithConfig

    main

    You can customize snapshot behavior using snaps.WithConfig(...). This can be applied to a specific test or used to create a configured instance for multiple calls.

    Available Configuration Options:

    • snaps.Dir(path): Sets the snapshot directory (relative or absolute).
    • snaps.Filename(name): Sets the snapshot filename.
    • snaps.Ext(ext): Sets the file extension (e.g., .json).
    • snaps.Update(bool): Programmatically controls whether to update snapshots.
    • snaps.Label(label): Adds a label to the end of snapshot IDs.
    • snaps.Serializer(func(any) string): Custom serializer for non-structured snapshots.
    • snaps.Raw(): Helper serializer that uses fmt.Sprint without formatting.
    • snaps.JSON(snaps.JSONConfig{...}): Configures JSON formatting:
      • Width: Max width before wrapping (default: 80).
      • Indent: Indentation string (default: 1 space).
      • SortKeys: Whether to sort keys alphabetically (default: true).
    s := snaps.WithConfig(
      snaps.Dir("my_dir"),
      snaps.Filename("json_file"),
      snaps.Ext(".json"),
      snaps.Label("JSON"),
      snaps.Update(false),
      snaps.JSON(snaps.JSONConfig{
        Width:    80,
        Indent:   "    ",
        SortKeys: false,
      }),
    )
    
    s.MatchJSON(t, `{"hello":"world"}`)
  10. Known limitations of go-snaps

    main

    Be aware of the following constraints:

    • Path Tracking: Running a specific test file via go test ./my_test.go prevents go-snaps from tracking the path, which may cause snapshots to be incorrectly marked as obsolete.
    • Line Endings: go-snaps does not handle CRLF line endings. Windows users should convert line endings to LF.
    • Trimpath Compatibility: When using go test -trimpath ./..., go-snaps cannot determine the snapshot path automatically and relies on the current working directory. You can override this by providing an absolute path using snaps.WithConfig(snaps.Dir("/some/absolute/path")).