Skip to content

Commit

Permalink
support value snapshot assertion
Browse files Browse the repository at this point in the history
  • Loading branch information
ysmood committed Oct 13, 2023
1 parent 32d829d commit d276850
Show file tree
Hide file tree
Showing 7 changed files with 161 additions and 3 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
*.out
*.test
tmp/
tmp/
.snapshots/
8 changes: 8 additions & 0 deletions .snapshots/TestSnapshots.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
{
"Name": "b",
"Value": 1
}
{
"Name": "a",
"Value": "ok"
}
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ An enjoyable golang test framework.
- Fluent API design that takes the full advantage of IDE
- Handy assertion helpers
- Handy utils for testing
- Value snapshot assertion
- Customizable assertion error output

## Guides
Expand Down
10 changes: 9 additions & 1 deletion got.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ type G struct {
Testable
Assertions
Utils

snapshots *snapshots
}

// Setup returns a helper to init G instance
Expand All @@ -51,11 +53,17 @@ func T(t Testable) G {
// New G instance
func New(t Testable) G {
eh := NewDefaultAssertionError(gop.ThemeDefault, diff.ThemeDefault)
return G{

g := G{
t,
Assertions{Testable: t, ErrorHandler: eh},
Utils{t},
&snapshots{},
}

g.loadSnapshots()

return g
}

// DefaultFlags will set the "go test" flag if not yet presented.
Expand Down
8 changes: 7 additions & 1 deletion setup_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,15 @@ type mock struct {
msg string
cleanupList []func()
recover bool
name string
}

func (m *mock) Name() string { return "mock" }
func (m *mock) Name() string {
if m.name == "" {
return "mock"
}
return m.name
}
func (m *mock) Skipped() bool { return m.skipped }
func (m *mock) Failed() bool { return m.failed }
func (m *mock) Helper() {}
Expand Down
103 changes: 103 additions & 0 deletions snapshots.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
package got

import (
"encoding/json"
"io"
"os"
"path/filepath"
"regexp"
"strings"
"sync"
)

type snapshots struct {
list *sync.Map
file io.ReadWriter
}

type snapshot struct {
Name string
Value interface{}
}

// snapshotsFilePath returns the path of the snapshot file for current test.
func (g G) snapshotsFilePath() string {
return filepath.Join(".snapshots", escapeFileName(g.Name())+".txt")
}

func (g G) loadSnapshots() {
p := g.snapshotsFilePath()

g.snapshots.list = &sync.Map{}

if g.PathExists(p) {
f, err := os.OpenFile(p, os.O_RDWR, 0755)
g.E(err)
g.snapshots.file = f
} else {
return
}

dec := json.NewDecoder(g.snapshots.file)

for {
var data snapshot
err := dec.Decode(&data)
if err == io.EOF {
return
}
g.E(err)
g.snapshots.list.Store(data.Name, data.Value)
}
}

// Snapshot asserts that x equals the snapshot with the specified name, name should be unique.
// It can only compare JSON serializable types.
// It will create a new snapshot if the name is not found.
// The snapshot will be saved to ".snapshots/{TEST_NAME}" beside the test file,
// TEST_NAME is the current test name.
// To update the snapshot, just delete the corresponding file.
func (g G) Snapshot(name string, x interface{}) {
g.Helper()

if y, ok := g.snapshots.list.Load(name); ok {
g.Eq(x, y)
return
}

g.snapshots.list.Store(name, x)

g.Cleanup(func() {
if g.snapshots.file == nil {
p := g.snapshotsFilePath()

err := os.MkdirAll(filepath.Dir(p), 0755)
g.E(err)

f, err := os.Create(p)
g.E(err)
g.snapshots.file = f
}

enc := json.NewEncoder(g.snapshots.file)
enc.SetIndent("", " ")
g.E(enc.Encode(snapshot{name, x}))
})
}

func escapeFileName(fileName string) string {
// Define the invalid characters for both Windows and Unix
invalidChars := `< > : " / \ | ? *`

// Replace the invalid characters with an underscore
regex := "[" + regexp.QuoteMeta(invalidChars) + "]"
escapedFileName := regexp.MustCompile(regex).ReplaceAllString(fileName, "_")

// Remove any leading or trailing spaces or dots
escapedFileName = strings.Trim(escapedFileName, " .")

// Remove consecutive dots
escapedFileName = regexp.MustCompile(`\.{2,}`).ReplaceAllString(escapedFileName, ".")

return escapedFileName
}
31 changes: 31 additions & 0 deletions snapshots_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package got_test

import (
"os"
"path/filepath"
"testing"

"github.com/ysmood/got"
)

func TestSnapshots(t *testing.T) {
m := &mock{t: t, name: t.Name()}
g := got.T(m)

g.Snapshot("a", "ok")
g.Snapshot("b", 1)

g.Snapshot("a", "no")
m.check(`"no" ⦗not ==⦘ "ok"`)
}

func TestSnapshotsCreate(t *testing.T) {
err := os.RemoveAll(filepath.Join(".snapshots", "TestSnapshotsCreate.txt"))
if err != nil {
panic(err)
}

g := got.T(t)

g.Snapshot("a", "ok")
}

0 comments on commit d276850

Please sign in to comment.