util/utiltest/utiltest.go (57 lines of code) (raw):

package utiltest import ( "errors" "os" "testing" "github.com/google/go-cmp/cmp" "github.com/kr/pretty" ) // BytesFromFile returns file as bytes; propagates err (e.g. file does not exist) as test failure reason func BytesFromFile(t *testing.T, filepath string) []byte { bytes, err := os.ReadFile(filepath) if err != nil { t.Fatalf("readFile(%q) err: %v", filepath, err) } return bytes } const draftSnapshotFileSuffix = ".draft" // testReporter is a subset of *testing.T, // defines minimum interface for reporting test failures and logging. type testReporter interface { Logf(format string, args ...interface{}) Errorf(format string, args ...interface{}) Error(args ...any) Helper() } func makeSnapshotDraftFilepath(snapshotFilepath string) string { return snapshotFilepath + draftSnapshotFileSuffix } func writeSnapshotDraft(t testReporter, filepath string, snapshot string) { t.Helper() draftFilepath := makeSnapshotDraftFilepath(filepath) if err := os.WriteFile(draftFilepath, []byte(snapshot), 0644); err != nil { t.Error(err) return } t.Logf("Remove %q suffix from %q actual data snapshot to make test pass.", draftSnapshotFileSuffix, draftFilepath) } func removeSnapshotDraft(filepath string) { draftFilepath := makeSnapshotDraftFilepath(filepath) os.Remove(draftFilepath) } // MatchSnapshot compares the actual data against a stored snapshot file. // // If the snapshot file doesn't exist, it creates a new draft file // (with a .draft suffix) containing the actual data and marks test failed. // // If the snapshot file exists but its content differs from the actual data, // it updates the draft file with the actual data, reports test failure and // instructs on how to update the snapshot. // // If the snapshot file exists and matches the actual data, it ensures // any existing draft file is removed and the test passes for this check. func MatchSnapshot(t testReporter, actual any, snapshotFilepath string) { t.Helper() nextSnapshot := pretty.Sprint(actual) prevSnapshotBytes, err := os.ReadFile(snapshotFilepath) if errors.Is(err, os.ErrNotExist) { writeSnapshotDraft(t, snapshotFilepath, nextSnapshot) t.Errorf("Snapshot file %q does not exist", snapshotFilepath) return } else if err != nil { t.Error(err) return } if diff := cmp.Diff(string(prevSnapshotBytes), nextSnapshot); diff != "" { writeSnapshotDraft(t, snapshotFilepath, nextSnapshot) t.Errorf("Snapshot file %q is different from actual data:\n%s", snapshotFilepath, diff) } else { removeSnapshotDraft(snapshotFilepath) } }