feat(options): add optional Options arguments to New() function (#14)

Enables simpler creation of custom *Golden instances, as you can just pass in the custom values to New(), rather than modifying fields after increating the Golden instance.
This commit is contained in:
2025-04-05 12:28:57 +01:00
committed by GitHub
parent 8a88e5174b
commit 550ba17fb0
4 changed files with 254 additions and 51 deletions

View File

@@ -257,14 +257,21 @@ type Golden struct {
} }
// New returns a new *Golden instance with default values correctly populated. // New returns a new *Golden instance with default values correctly populated.
func New() *Golden { // It accepts zero or more Option functions that can modify the default values.
return &Golden{ func New(opts ...Option) *Golden {
g := &Golden{
DirMode: DefaultDirMode, DirMode: DefaultDirMode,
FileMode: DefaultFileMode, FileMode: DefaultFileMode,
Suffix: DefaultSuffix, Suffix: DefaultSuffix,
Dirname: DefaultDirname, Dirname: DefaultDirname,
UpdateFunc: DefaultUpdateFunc, UpdateFunc: DefaultUpdateFunc,
} }
for _, opt := range opts {
opt(g)
}
return g
} }
// Do is a convenience function for calling Update(), Set(), and Get() in a // Do is a convenience function for calling Update(), Set(), and Get() in a

View File

@@ -11,6 +11,37 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
// assertSameFunc asserts that two functions are the same by comparing their
// function names via reflection.
func assertSameFunc(t *testing.T, want, got interface{}) {
t.Helper()
// Verify both arguments are functions
wantType := reflect.TypeOf(want)
gotType := reflect.TypeOf(got)
if wantType.Kind() != reflect.Func {
t.Fatalf(
"assertSameFunc: 'want' argument is not a function, got %s",
wantType.Kind(),
)
}
if gotType.Kind() != reflect.Func {
t.Fatalf(
"assertSameFunc: 'got' argument is not a function, got %s",
gotType.Kind(),
)
}
gotFP := reflect.ValueOf(got).Pointer()
gotFuncName := runtime.FuncForPC(gotFP).Name()
wantFP := reflect.ValueOf(want).Pointer()
wantFuncName := runtime.FuncForPC(wantFP).Name()
assert.Equal(t, wantFuncName, gotFuncName)
}
func TestDefaults(t *testing.T) { func TestDefaults(t *testing.T) {
t.Run("Default", func(t *testing.T) { t.Run("Default", func(t *testing.T) {
assert.IsType(t, &Golden{}, Default) assert.IsType(t, &Golden{}, Default)
@@ -19,15 +50,7 @@ func TestDefaults(t *testing.T) {
assert.Equal(t, DefaultFileMode, Default.FileMode) assert.Equal(t, DefaultFileMode, Default.FileMode)
assert.Equal(t, DefaultSuffix, Default.Suffix) assert.Equal(t, DefaultSuffix, Default.Suffix)
assert.Equal(t, DefaultDirname, Default.Dirname) assert.Equal(t, DefaultDirname, Default.Dirname)
assertSameFunc(t, EnvUpdateFunc, Default.UpdateFunc)
// Use runtime.FuncForPC() to verify the UpdateFunc value is set to
// the EnvUpdateFunc function by default.
gotFP := reflect.ValueOf(Default.UpdateFunc).Pointer()
gotFuncName := runtime.FuncForPC(gotFP).Name()
wantFP := reflect.ValueOf(EnvUpdateFunc).Pointer()
wantFuncName := runtime.FuncForPC(wantFP).Name()
assert.Equal(t, wantFuncName, gotFuncName)
}) })
t.Run("DefaultDirMode", func(t *testing.T) { t.Run("DefaultDirMode", func(t *testing.T) {
@@ -47,57 +70,129 @@ func TestDefaults(t *testing.T) {
}) })
t.Run("DefaultUpdateFunc", func(t *testing.T) { t.Run("DefaultUpdateFunc", func(t *testing.T) {
gotFP := reflect.ValueOf(DefaultUpdateFunc).Pointer() assertSameFunc(t, EnvUpdateFunc, DefaultUpdateFunc)
gotFuncName := runtime.FuncForPC(gotFP).Name() })
wantFP := reflect.ValueOf(EnvUpdateFunc).Pointer()
wantFuncName := runtime.FuncForPC(wantFP).Name() t.Run("customized Default* variables", func(t *testing.T) {
assert.Equal(t, wantFuncName, gotFuncName) // Capture the default values before we change them.
defaultDirMode := DefaultDirMode
defaultFileMode := DefaultFileMode
defaultSuffix := DefaultSuffix
defaultDirname := DefaultDirname
defaultUpdateFunc := DefaultUpdateFunc
// Restore the default values after the test.
t.Cleanup(func() {
DefaultDirMode = defaultDirMode
DefaultFileMode = defaultFileMode
DefaultSuffix = defaultSuffix
DefaultDirname = defaultDirname
DefaultUpdateFunc = defaultUpdateFunc
})
// Set all the default values to new values.
DefaultDirMode = os.FileMode(0o700)
DefaultFileMode = os.FileMode(0o600)
DefaultSuffix = ".gold"
DefaultDirname = "goldenfiles"
updateFunc := func() bool { return true }
DefaultUpdateFunc = updateFunc
// Create a new Golden instance with the new values.
got := New()
assert.Equal(t, DefaultDirMode, got.DirMode)
assert.Equal(t, DefaultFileMode, got.FileMode)
assert.Equal(t, DefaultSuffix, got.Suffix)
assert.Equal(t, DefaultDirname, got.Dirname)
assertSameFunc(t, updateFunc, got.UpdateFunc)
}) })
} }
// TestNew is a horribly hack to test that the New() function uses the
// package-level Default* variables.
func TestNew(t *testing.T) { func TestNew(t *testing.T) {
// Capture the default values before we change them. t.Run("defaults (no options)", func(t *testing.T) {
defaultDirMode := DefaultDirMode g := New()
defaultFileMode := DefaultFileMode assert.Equal(t, DefaultDirMode, g.DirMode)
defaultSuffix := DefaultSuffix assert.Equal(t, DefaultFileMode, g.FileMode)
defaultDirname := DefaultDirname assert.Equal(t, DefaultSuffix, g.Suffix)
defaultUpdateFunc := DefaultUpdateFunc assert.Equal(t, DefaultDirname, g.Dirname)
assertSameFunc(t, EnvUpdateFunc, g.UpdateFunc)
// Restore the default values after the test.
t.Cleanup(func() {
DefaultDirMode = defaultDirMode
DefaultFileMode = defaultFileMode
DefaultSuffix = defaultSuffix
DefaultDirname = defaultDirname
DefaultUpdateFunc = defaultUpdateFunc
}) })
// Set all the default values to new values. // Test each option individually
DefaultDirMode = os.FileMode(0o700) t.Run("WithDirMode", func(t *testing.T) {
DefaultFileMode = os.FileMode(0o600) customMode := os.FileMode(0o700)
DefaultSuffix = ".gold" g := New(WithDirMode(customMode))
DefaultDirname = "goldenfiles" assert.Equal(t, customMode, g.DirMode)
assert.Equal(t, DefaultFileMode, g.FileMode)
assert.Equal(t, DefaultSuffix, g.Suffix)
assert.Equal(t, DefaultDirname, g.Dirname)
assertSameFunc(t, EnvUpdateFunc, g.UpdateFunc)
})
updateFunc := func() bool { return true } t.Run("WithFileMode", func(t *testing.T) {
DefaultUpdateFunc = updateFunc customMode := os.FileMode(0o600)
g := New(WithFileMode(customMode))
assert.Equal(t, DefaultDirMode, g.DirMode)
assert.Equal(t, customMode, g.FileMode)
assert.Equal(t, DefaultSuffix, g.Suffix)
assert.Equal(t, DefaultDirname, g.Dirname)
assertSameFunc(t, EnvUpdateFunc, g.UpdateFunc)
})
// Create a new Golden instance with the new values. t.Run("WithSuffix", func(t *testing.T) {
got := New() customSuffix := ".my-suffix"
g := New(WithSuffix(customSuffix))
assert.Equal(t, DefaultDirMode, g.DirMode)
assert.Equal(t, DefaultFileMode, g.FileMode)
assert.Equal(t, customSuffix, g.Suffix)
assert.Equal(t, DefaultDirname, g.Dirname)
assertSameFunc(t, EnvUpdateFunc, g.UpdateFunc)
})
assert.Equal(t, DefaultDirMode, got.DirMode) t.Run("WithDirname", func(t *testing.T) {
assert.Equal(t, DefaultFileMode, got.FileMode) customDirname := "fixtures/generated"
assert.Equal(t, DefaultSuffix, got.Suffix) g := New(WithDirname(customDirname))
assert.Equal(t, DefaultDirname, got.Dirname) assert.Equal(t, DefaultDirMode, g.DirMode)
assert.Equal(t, DefaultFileMode, g.FileMode)
assert.Equal(t, DefaultSuffix, g.Suffix)
assert.Equal(t, customDirname, g.Dirname)
assertSameFunc(t, EnvUpdateFunc, g.UpdateFunc)
})
// Verify the UpdateFunc value is set to the new value. t.Run("WithUpdateFunc", func(t *testing.T) {
gotFP := reflect.ValueOf(got.UpdateFunc).Pointer() customUpdateFunc := func() bool { return true }
gotFuncName := runtime.FuncForPC(gotFP).Name() g := New(WithUpdateFunc(customUpdateFunc))
wantFP := reflect.ValueOf(updateFunc).Pointer() assert.Equal(t, DefaultDirMode, g.DirMode)
wantFuncName := runtime.FuncForPC(wantFP).Name() assert.Equal(t, DefaultFileMode, g.FileMode)
assert.Equal(t, DefaultSuffix, g.Suffix)
assert.Equal(t, DefaultDirname, g.Dirname)
assertSameFunc(t, customUpdateFunc, g.UpdateFunc)
})
assert.Equal(t, wantFuncName, gotFuncName) // Test multiple options at once
t.Run("MultipleOptions", func(t *testing.T) {
customDirMode := os.FileMode(0o700)
customFileMode := os.FileMode(0o600)
customSuffix := ".fixture"
customDirname := "fixtures"
customUpdateFunc := func() bool { return true }
g := New(
WithDirMode(customDirMode),
WithFileMode(customFileMode),
WithSuffix(customSuffix),
WithDirname(customDirname),
WithUpdateFunc(customUpdateFunc),
)
assert.Equal(t, customDirMode, g.DirMode)
assert.Equal(t, customFileMode, g.FileMode)
assert.Equal(t, customSuffix, g.Suffix)
assert.Equal(t, customDirname, g.Dirname)
assertSameFunc(t, customUpdateFunc, g.UpdateFunc)
})
} }
func TestDo(t *testing.T) { func TestDo(t *testing.T) {

43
options.go Normal file
View File

@@ -0,0 +1,43 @@
package golden
import (
"os"
)
// Option is a function that modifies a Golden instance.
type Option func(*Golden)
// WithDirMode sets the directory mode for a Golden instance.
func WithDirMode(mode os.FileMode) Option {
return func(g *Golden) {
g.DirMode = mode
}
}
// WithFileMode sets the file mode for a Golden instance.
func WithFileMode(mode os.FileMode) Option {
return func(g *Golden) {
g.FileMode = mode
}
}
// WithSuffix sets the file suffix for a Golden instance.
func WithSuffix(suffix string) Option {
return func(g *Golden) {
g.Suffix = suffix
}
}
// WithDirname sets the directory name for a Golden instance.
func WithDirname(dirname string) Option {
return func(g *Golden) {
g.Dirname = dirname
}
}
// WithUpdateFunc sets the update function for a Golden instance.
func WithUpdateFunc(updateFunc UpdateFunc) Option {
return func(g *Golden) {
g.UpdateFunc = updateFunc
}
}

58
options_test.go Normal file
View File

@@ -0,0 +1,58 @@
package golden
import (
"os"
"testing"
"github.com/stretchr/testify/assert"
)
func TestWithDirMode(t *testing.T) {
customMode := os.FileMode(0o700)
g := &Golden{}
opt := WithDirMode(customMode)
opt(g)
assert.Equal(t, customMode, g.DirMode)
}
func TestWithFileMode(t *testing.T) {
customMode := os.FileMode(0o600)
g := &Golden{}
opt := WithFileMode(customMode)
opt(g)
assert.Equal(t, customMode, g.FileMode)
}
func TestWithSuffix(t *testing.T) {
customSuffix := ".custom"
g := &Golden{}
opt := WithSuffix(customSuffix)
opt(g)
assert.Equal(t, customSuffix, g.Suffix)
}
func TestWithDirname(t *testing.T) {
customDirname := "custom-testdata"
g := &Golden{}
opt := WithDirname(customDirname)
opt(g)
assert.Equal(t, customDirname, g.Dirname)
}
func TestWithUpdateFunc(t *testing.T) {
customUpdateFunc := func() bool { return true }
g := &Golden{}
opt := WithUpdateFunc(customUpdateFunc)
opt(g)
assertSameFunc(t, customUpdateFunc, g.UpdateFunc)
}