Skip to content

Commit b3106d7

Browse files
allow testing for functional options (#1023)
* allow testing for functional options * add linting docs * use reflect magic to obtain FunctionalOption name Co-authored-by: dillonstreator <[email protected]> --------- Co-authored-by: dillonstreator <[email protected]>
1 parent 437071b commit b3106d7

File tree

2 files changed

+176
-0
lines changed

2 files changed

+176
-0
lines changed

Diff for: mock/mock.go

+119
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package mock
33
import (
44
"errors"
55
"fmt"
6+
"path"
67
"reflect"
78
"regexp"
89
"runtime"
@@ -13,6 +14,7 @@ import (
1314
"github.com/davecgh/go-spew/spew"
1415
"github.com/pmezard/go-difflib/difflib"
1516
"github.com/stretchr/objx"
17+
1618
"github.com/stretchr/testify/assert"
1719
)
1820

@@ -424,6 +426,10 @@ func callString(method string, arguments Arguments, includeArgumentValues bool)
424426
if includeArgumentValues {
425427
var argVals []string
426428
for argIndex, arg := range arguments {
429+
if _, ok := arg.(*FunctionalOptionsArgument); ok {
430+
argVals = append(argVals, fmt.Sprintf("%d: %s", argIndex, arg))
431+
continue
432+
}
427433
argVals = append(argVals, fmt.Sprintf("%d: %#v", argIndex, arg))
428434
}
429435
argValsString = fmt.Sprintf("\n\t\t%s", strings.Join(argVals, "\n\t\t"))
@@ -780,6 +786,34 @@ func IsType(t interface{}) *IsTypeArgument {
780786
return &IsTypeArgument{t: t}
781787
}
782788

789+
// FunctionalOptionsArgument is a struct that contains the type and value of an functional option argument
790+
// for use when type checking.
791+
type FunctionalOptionsArgument struct {
792+
value interface{}
793+
}
794+
795+
// String returns the string representation of FunctionalOptionsArgument
796+
func (f *FunctionalOptionsArgument) String() string {
797+
var name string
798+
tValue := reflect.ValueOf(f.value)
799+
if tValue.Len() > 0 {
800+
name = "[]" + reflect.TypeOf(tValue.Index(0).Interface()).String()
801+
}
802+
803+
return strings.Replace(fmt.Sprintf("%#v", f.value), "[]interface {}", name, 1)
804+
}
805+
806+
// FunctionalOptions returns an FunctionalOptionsArgument object containing the functional option type
807+
// and the values to check of
808+
//
809+
// For example:
810+
// Assert(t, FunctionalOptions("[]foo.FunctionalOption", foo.Opt1(), foo.Opt2()))
811+
func FunctionalOptions(value ...interface{}) *FunctionalOptionsArgument {
812+
return &FunctionalOptionsArgument{
813+
value: value,
814+
}
815+
}
816+
783817
// argumentMatcher performs custom argument matching, returning whether or
784818
// not the argument is matched by the expectation fixture function.
785819
type argumentMatcher struct {
@@ -926,6 +960,29 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
926960
differences++
927961
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, reflect.TypeOf(t).Name(), reflect.TypeOf(actual).Name(), actualFmt)
928962
}
963+
} else if reflect.TypeOf(expected) == reflect.TypeOf((*FunctionalOptionsArgument)(nil)) {
964+
t := expected.(*FunctionalOptionsArgument).value
965+
966+
var name string
967+
tValue := reflect.ValueOf(t)
968+
if tValue.Len() > 0 {
969+
name = "[]" + reflect.TypeOf(tValue.Index(0).Interface()).String()
970+
}
971+
972+
tName := reflect.TypeOf(t).Name()
973+
if name != reflect.TypeOf(actual).String() && tValue.Len() != 0 {
974+
differences++
975+
output = fmt.Sprintf("%s\t%d: FAIL: type %s != type %s - %s\n", output, i, tName, reflect.TypeOf(actual).Name(), actualFmt)
976+
} else {
977+
if ef, af := assertOpts(t, actual); ef == "" && af == "" {
978+
// match
979+
output = fmt.Sprintf("%s\t%d: PASS: %s == %s\n", output, i, tName, tName)
980+
} else {
981+
// not match
982+
differences++
983+
output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, af, ef)
984+
}
985+
}
929986
} else {
930987
// normal checking
931988

@@ -1102,3 +1159,65 @@ var spewConfig = spew.ConfigState{
11021159
type tHelper interface {
11031160
Helper()
11041161
}
1162+
1163+
func assertOpts(expected, actual interface{}) (expectedFmt, actualFmt string) {
1164+
expectedOpts := reflect.ValueOf(expected)
1165+
actualOpts := reflect.ValueOf(actual)
1166+
var expectedNames []string
1167+
for i := 0; i < expectedOpts.Len(); i++ {
1168+
expectedNames = append(expectedNames, funcName(expectedOpts.Index(i).Interface()))
1169+
}
1170+
var actualNames []string
1171+
for i := 0; i < actualOpts.Len(); i++ {
1172+
actualNames = append(actualNames, funcName(actualOpts.Index(i).Interface()))
1173+
}
1174+
if !assert.ObjectsAreEqual(expectedNames, actualNames) {
1175+
expectedFmt = fmt.Sprintf("%v", expectedNames)
1176+
actualFmt = fmt.Sprintf("%v", actualNames)
1177+
return
1178+
}
1179+
1180+
for i := 0; i < expectedOpts.Len(); i++ {
1181+
expectedOpt := expectedOpts.Index(i).Interface()
1182+
actualOpt := actualOpts.Index(i).Interface()
1183+
1184+
expectedFunc := expectedNames[i]
1185+
actualFunc := actualNames[i]
1186+
if expectedFunc != actualFunc {
1187+
expectedFmt = expectedFunc
1188+
actualFmt = actualFunc
1189+
return
1190+
}
1191+
1192+
ot := reflect.TypeOf(expectedOpt)
1193+
var expectedValues []reflect.Value
1194+
var actualValues []reflect.Value
1195+
if ot.NumIn() == 0 {
1196+
return
1197+
}
1198+
1199+
for i := 0; i < ot.NumIn(); i++ {
1200+
vt := ot.In(i).Elem()
1201+
expectedValues = append(expectedValues, reflect.New(vt))
1202+
actualValues = append(actualValues, reflect.New(vt))
1203+
}
1204+
1205+
reflect.ValueOf(expectedOpt).Call(expectedValues)
1206+
reflect.ValueOf(actualOpt).Call(actualValues)
1207+
1208+
for i := 0; i < ot.NumIn(); i++ {
1209+
if !assert.ObjectsAreEqual(expectedValues[i].Interface(), actualValues[i].Interface()) {
1210+
expectedFmt = fmt.Sprintf("%s %+v", expectedNames[i], expectedValues[i].Interface())
1211+
actualFmt = fmt.Sprintf("%s %+v", expectedNames[i], actualValues[i].Interface())
1212+
return
1213+
}
1214+
}
1215+
}
1216+
1217+
return "", ""
1218+
}
1219+
1220+
func funcName(opt interface{}) string {
1221+
n := runtime.FuncForPC(reflect.ValueOf(opt).Pointer()).Name()
1222+
return strings.TrimSuffix(path.Base(n), path.Ext(n))
1223+
}

Diff for: mock/mock_test.go

+57
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,29 @@ func (i *TestExampleImplementation) TheExampleMethod(a, b, c int) (int, error) {
3232
return args.Int(0), errors.New("Whoops")
3333
}
3434

35+
type options struct {
36+
num int
37+
str string
38+
}
39+
40+
type OptionFn func(*options)
41+
42+
func OpNum(n int) OptionFn {
43+
return func(o *options) {
44+
o.num = n
45+
}
46+
}
47+
48+
func OpStr(s string) OptionFn {
49+
return func(o *options) {
50+
o.str = s
51+
}
52+
}
53+
func (i *TestExampleImplementation) TheExampleMethodFunctionalOptions(x string, opts ...OptionFn) error {
54+
args := i.Called(x, opts)
55+
return args.Error(0)
56+
}
57+
3558
//go:noinline
3659
func (i *TestExampleImplementation) TheExampleMethod2(yesorno bool) {
3760
i.Called(yesorno)
@@ -1415,6 +1438,40 @@ func Test_Mock_AssertExpectationsCustomType(t *testing.T) {
14151438

14161439
}
14171440

1441+
func Test_Mock_AssertExpectationsFunctionalOptionsType(t *testing.T) {
1442+
1443+
var mockedService = new(TestExampleImplementation)
1444+
1445+
mockedService.On("TheExampleMethodFunctionalOptions", "test", FunctionalOptions(OpNum(1), OpStr("foo"))).Return(nil).Once()
1446+
1447+
tt := new(testing.T)
1448+
assert.False(t, mockedService.AssertExpectations(tt))
1449+
1450+
// make the call now
1451+
mockedService.TheExampleMethodFunctionalOptions("test", OpNum(1), OpStr("foo"))
1452+
1453+
// now assert expectations
1454+
assert.True(t, mockedService.AssertExpectations(tt))
1455+
1456+
}
1457+
1458+
func Test_Mock_AssertExpectationsFunctionalOptionsType_Empty(t *testing.T) {
1459+
1460+
var mockedService = new(TestExampleImplementation)
1461+
1462+
mockedService.On("TheExampleMethodFunctionalOptions", "test", FunctionalOptions()).Return(nil).Once()
1463+
1464+
tt := new(testing.T)
1465+
assert.False(t, mockedService.AssertExpectations(tt))
1466+
1467+
// make the call now
1468+
mockedService.TheExampleMethodFunctionalOptions("test")
1469+
1470+
// now assert expectations
1471+
assert.True(t, mockedService.AssertExpectations(tt))
1472+
1473+
}
1474+
14181475
func Test_Mock_AssertExpectations_With_Repeatability(t *testing.T) {
14191476

14201477
var mockedService = new(TestExampleImplementation)

0 commit comments

Comments
 (0)