Files
go-golden/marshaling_asserter.go
Jim Myhrberg 4e07a1a657 wip(assertion): incomplete refactor of tests and assertion helpers
Tests have started using github.com/jimeh/go-mocktesting which allows
testing unhappy paths where t.Fatal() and related functions are called.
2021-12-28 02:24:00 +00:00

134 lines
3.7 KiB
Go

package golden
import (
"reflect"
"strings"
"github.com/jimeh/go-golden/sanitize"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
type (
MarshalFunc func(interface{}) ([]byte, error)
UnmarshalFunc func([]byte, interface{}) error
)
// MarshalingAsserter allows building marshaling asserters by providing
// functions which marshal and unmarshal objects.
type MarshalingAsserter struct {
// Golden is the *Golden instance used to read/write golden files.
Golden Golden
// Name of the format the MarshalAsserter handles.
Format string
// GoldName is the name of the golden file used when marshaling. This is by
// default set based on Format using NewMarshalAsserter. For example if
// Format is set to "JSON", GoldName will be set to "marshaled_json" by
// default.
GoldName string
// MarshalFunc is the function used to marshal given objects.
MarshalFunc MarshalFunc
// UnmarshalFunc is the function used to unmarshal given objects.
UnmarshalFunc UnmarshalFunc
// NormalizeLineBreaks determines if Windows' CRLF (\r\n) and MacOS Classic
// CR (\r) line breaks are replaced with Unix's LF (\n) line breaks. This
// ensures marshaling assertions work across different platforms.
NormalizeLineBreaks bool
}
// New returns a new MarshalingAsserter.
func NewMarshalingAsserter(
golden Golden,
format string,
marshalFunc MarshalFunc,
unmarshalFunc UnmarshalFunc,
normalizeLineBreaks bool,
) *MarshalingAsserter {
goldName := "marshaled_" + strings.ToLower(sanitize.Filename(format))
return &MarshalingAsserter{
Golden: golden,
Format: format,
GoldName: goldName,
MarshalFunc: marshalFunc,
UnmarshalFunc: unmarshalFunc,
NormalizeLineBreaks: normalizeLineBreaks,
}
}
// Marshaling asserts that the given "v" value marshals via the provided encoder
// to an expected value fetched from a golden file on disk, and then verifies
// that the marshaled result produces a value that is equal to "v" when
// unmarshaled.
//
// Used for objects that do NOT change when they are marshaled and unmarshaled.
func (s *MarshalingAsserter) Marshaling(t TestingT, v interface{}) {
t.Helper()
s.MarshalingP(t, v, v)
}
// MarshalingP asserts that the given "v" value marshals via the provided
// encoder to an expected value fetched from a golden file on disk, and then
// verifies that the marshaled result produces a value that is equal to "want"
// when unmarshaled.
//
// Used for objects that change when they are marshaled and unmarshaled.
func (s *MarshalingAsserter) MarshalingP(
t TestingT,
v interface{},
want interface{},
) {
t.Helper()
if reflect.ValueOf(want).Kind() != reflect.Ptr {
require.FailNowf(t,
"golden: only pointer types can be asserted",
"%T is not a pointer type", want,
)
}
marshaled, err := s.MarshalFunc(v)
require.NoErrorf(t,
err, "golden: failed to %s marshal %T: %+v", s.Format, v, v,
)
if s.NormalizeLineBreaks {
marshaled = sanitize.LineBreaks(marshaled)
}
if s.Golden.Update() {
s.Golden.SetP(t, s.GoldName, marshaled)
}
gold := s.Golden.GetP(t, s.GoldName)
if s.NormalizeLineBreaks {
gold = sanitize.LineBreaks(gold)
}
switch strings.ToLower(s.Format) {
case "json":
assert.JSONEq(t, string(gold), string(marshaled))
case "yaml", "yml":
assert.YAMLEq(t, string(gold), string(marshaled))
default:
assert.Equal(t, string(gold), string(marshaled))
}
got := reflect.New(reflect.TypeOf(want).Elem()).Interface()
err = s.UnmarshalFunc(gold, got)
f := s.Golden.FileP(t, s.GoldName)
require.NoErrorf(t, err,
"golden: failed to %s unmarshal %T from %s", s.Format, got, f,
)
assert.Equalf(t, want, got,
"golden: unmarshaling from golden file does not match "+
"expected object; golden file: %s", f,
)
}