From 3622259ca37205d6ee877c32b8765feff96f9813 Mon Sep 17 00:00:00 2001 From: Tom Fleet Date: Tue, 19 Nov 2024 19:24:33 +0000 Subject: [PATCH] Use json.Marshaler over TextMarshaler if it's present (#8) ## Summary --- README.md | 2 ++ snapshot.go | 8 +++++ snapshot_test.go | 29 +++++++++++++++++-- .../TestSnap/json_marshaler.snap.txt | 1 + 4 files changed, 38 insertions(+), 2 deletions(-) create mode 100644 testdata/snapshots/TestSnap/json_marshaler.snap.txt diff --git a/README.md b/README.md index e6ceeab..c719186 100644 --- a/README.md +++ b/README.md @@ -140,6 +140,7 @@ The files will be named automatically after the test: Because of this, it needs to know how to serialise your value (which could be basically any valid construct in Go) to plain text, so we follow a few basic rules in priority order: - **`snapshot.Snapper`:** If your type implements the `Snapper` interface, this is preferred over all other potential serialisation, this allows you to have total control over how your type is snapshotted, do whatever you like in the `Snap` method, just return a `[]byte` that you'd like to look at in the snapshot and thats it! +- **[json.Marshaler]:** If your type implements [json.Marshaler], this will be used and the snapshot will be a valid JSON file - **[encoding.TextMarshaler]:** If your type implements [encoding.TextMarshaler], this will be used to render your value to the snapshot - **[fmt.Stringer]:** If your type implements the [fmt.Stringer] interface, this is then used instead - **Primitive Types:** Any primitive type in Go (`bool`, `int`, `string` etc.) is serialised according to the `%v` verb in the [fmt] package @@ -155,5 +156,6 @@ This package was created with [copier] and the [FollowTheProcess/go_copier] proj [copier]: https://copier.readthedocs.io/en/stable/ [FollowTheProcess/go_copier]: https://github.com/FollowTheProcess/go_copier [fmt]: https://pkg.go.dev/fmt +[json.Marshaler]: https://pkg.go.dev/encoding/json#Marshaler [encoding.TextMarshaler]: https://pkg.go.dev/encoding#TextMarshaler [fmt.Stringer]: https://pkg.go.dev/fmt#Stringer diff --git a/snapshot.go b/snapshot.go index b2ef138..1dedac9 100644 --- a/snapshot.go +++ b/snapshot.go @@ -8,6 +8,7 @@ package snapshot import ( "bytes" "encoding" + "encoding/json" "errors" "fmt" "io/fs" @@ -75,6 +76,13 @@ func (s *Shotter) Snap(value any) { return } current.Write(content) + case json.Marshaler: + content, err := val.MarshalJSON() + if err != nil { + s.tb.Fatalf("%T implements json.Marshaler but MarshalJSON() returned an error: %v", val, err) + return + } + current.Write(content) case encoding.TextMarshaler: content, err := val.MarshalText() if err != nil { diff --git a/snapshot_test.go b/snapshot_test.go index 4ea1b82..bf8d9a9 100644 --- a/snapshot_test.go +++ b/snapshot_test.go @@ -67,14 +67,14 @@ func (e explosion) Snap() ([]byte, error) { // nosnap has no Snap implementation. type nosnap struct{} -// textMarshaler is a struct that implements encoding.TextMarshaller. +// textMarshaler is a struct that implements encoding.TextMarshaler. type textMarshaler struct{} func (t textMarshaler) MarshalText() (text []byte, err error) { return []byte("MarshalText() called\n"), nil } -// errMarshaler is a struct that implements encoding.TextMarshaller, but always returns an error. +// errMarshaler is a struct that implements encoding.TextMarshaler, but always returns an error. type errMarshaler struct{} func (t errMarshaler) MarshalText() (text []byte, err error) { @@ -88,6 +88,20 @@ func (s stringer) String() string { return "String() called\n" } +// jsonMarshaler is a struct that implements json.Marshaler. +type jsonMarshaler struct{} + +func (j jsonMarshaler) MarshalJSON() ([]byte, error) { + return []byte(`{"key": "value"}`), nil +} + +// errJSONMarshaler is a struct that implements json.Marshaler, but always returns an error. +type errJSONMarshaler struct{} + +func (e errJSONMarshaler) MarshalJSON() ([]byte, error) { + return nil, errors.New("MarshalJSON error") +} + func TestSnap(t *testing.T) { tests := []struct { value any // Value to snap @@ -165,6 +179,17 @@ func TestSnap(t *testing.T) { wantFail: false, existingSnap: "String() called\n", }, + { + name: "json marshaler", + value: jsonMarshaler{}, + wantFail: false, + existingSnap: `{"key": "value"}`, + }, + { + name: "json marshaler error", + value: errJSONMarshaler{}, + wantFail: true, + }, } for _, tt := range tests { diff --git a/testdata/snapshots/TestSnap/json_marshaler.snap.txt b/testdata/snapshots/TestSnap/json_marshaler.snap.txt new file mode 100644 index 0000000..9082d25 --- /dev/null +++ b/testdata/snapshots/TestSnap/json_marshaler.snap.txt @@ -0,0 +1 @@ +{"key": "value"} \ No newline at end of file