Merge pull request #227 from pippio/master

Add custom mock argument matching
pull/259/head
Ernesto Jiménez 2016-01-02 11:55:03 +01:00
commit 6905a3e663
2 changed files with 128 additions and 1 deletions

View File

@ -458,6 +458,47 @@ func AnythingOfType(t string) AnythingOfTypeArgument {
return AnythingOfTypeArgument(t)
}
// argumentMatcher performs custom argument matching, returning whether or
// not the argument is matched by the expectation fixture function.
type argumentMatcher struct {
// fn is a function which accepts one argument, and returns a bool.
fn reflect.Value
}
func (f argumentMatcher) Matches(argument interface{}) bool {
expectType := f.fn.Type().In(0)
if reflect.TypeOf(argument).AssignableTo(expectType) {
result := f.fn.Call([]reflect.Value{reflect.ValueOf(argument)})
return result[0].Bool()
} else {
return false
}
}
func (f argumentMatcher) String() string {
return fmt.Sprintf("func(%s) bool", f.fn.Type().In(0).Name())
}
// Builds an argumentMatcher from |fn|, which must be a function accepting a
// single argument (of any type) which returns a bool. If |fn| doesn't match
// the required signature, MathedBy() panics.
func MatchedBy(fn interface{}) argumentMatcher {
fnType := reflect.TypeOf(fn)
if fnType.Kind() != reflect.Func {
panic(fmt.Sprintf("assert: arguments: %s is not a func", fn))
}
if fnType.NumIn() != 1 {
panic(fmt.Sprintf("assert: arguments: %s does not take exactly one argument", fn))
}
if fnType.NumOut() != 1 || fnType.Out(0).Kind() != reflect.Bool {
panic(fmt.Sprintf("assert: arguments: %s does not return a bool", fn))
}
return argumentMatcher{fn: reflect.ValueOf(fn)}
}
// Get Returns the argument at the specified index.
func (args Arguments) Get(index int) interface{} {
if index+1 > len(args) {
@ -505,7 +546,14 @@ func (args Arguments) Diff(objects []interface{}) (string, int) {
expected = args[i]
}
if reflect.TypeOf(expected) == reflect.TypeOf((*AnythingOfTypeArgument)(nil)).Elem() {
if matcher, ok := expected.(argumentMatcher); ok {
if matcher.Matches(actual) {
output = fmt.Sprintf("%s\t%d: \u2705 %s matched by %s\n", output, i, actual, matcher)
} else {
differences++
output = fmt.Sprintf("%s\t%d: \u2705 %s not matched by %s\n", output, i, actual, matcher)
}
} else if reflect.TypeOf(expected) == reflect.TypeOf((*AnythingOfTypeArgument)(nil)).Elem() {
// type checking
if reflect.TypeOf(actual).Name() != string(expected.(AnythingOfTypeArgument)) && reflect.TypeOf(actual).String() != string(expected.(AnythingOfTypeArgument)) {

View File

@ -147,6 +147,63 @@ func Test_Mock_On_WithFuncArg(t *testing.T) {
})
}
func Test_Mock_On_WithIntArgMatcher(t *testing.T) {
var mockedService TestExampleImplementation
mockedService.On("TheExampleMethod",
MatchedBy(func(a int) bool {
return a == 1
}), MatchedBy(func(b int) bool {
return b == 2
}), MatchedBy(func(c int) bool {
return c == 3
})).Return(0, nil)
assert.Panics(t, func() {
mockedService.TheExampleMethod(1, 2, 4)
})
assert.Panics(t, func() {
mockedService.TheExampleMethod(2, 2, 3)
})
assert.NotPanics(t, func() {
mockedService.TheExampleMethod(1, 2, 3)
})
}
func Test_Mock_On_WithPtrArgMatcher(t *testing.T) {
var mockedService TestExampleImplementation
mockedService.On("TheExampleMethod3",
MatchedBy(func(a *ExampleType) bool { return a.ran == true }),
).Return(nil)
mockedService.On("TheExampleMethod3",
MatchedBy(func(a *ExampleType) bool { return a.ran == false }),
).Return(errors.New("error!"))
assert.Equal(t, mockedService.TheExampleMethod3(&ExampleType{true}), nil)
assert.EqualError(t, mockedService.TheExampleMethod3(&ExampleType{false}), "error!")
}
func Test_Mock_On_WithFuncArgMatcher(t *testing.T) {
var mockedService TestExampleImplementation
fixture1, fixture2 := errors.New("fixture1"), errors.New("fixture2")
mockedService.On("TheExampleMethodFunc",
MatchedBy(func(a func(string) error) bool { return a("string") == fixture1 }),
).Return(errors.New("fixture1"))
mockedService.On("TheExampleMethodFunc",
MatchedBy(func(a func(string) error) bool { return a("string") == fixture2 }),
).Return(errors.New("fixture2"))
assert.EqualError(t, mockedService.TheExampleMethodFunc(
func(string) error { return fixture1 }), "fixture1")
assert.EqualError(t, mockedService.TheExampleMethodFunc(
func(string) error { return fixture2 }), "fixture2")
}
func Test_Mock_On_WithVariadicFunc(t *testing.T) {
// make a test impl object
@ -937,6 +994,28 @@ func Test_Arguments_Diff_WithAnythingOfTypeArgument_Failing(t *testing.T) {
}
func Test_Arguments_Diff_WithArgMatcher(t *testing.T) {
matchFn := func(a int) bool {
return a == 123
}
var args Arguments = []interface{}{"string", MatchedBy(matchFn), true}
diff, count := args.Diff([]interface{}{"string", 124, true})
assert.Equal(t, 1, count)
assert.Contains(t, diff, `%!s(int=124) not matched by func(int) bool`)
diff, count = args.Diff([]interface{}{"string", false, true})
assert.Equal(t, 1, count)
assert.Contains(t, diff, `%!s(bool=false) not matched by func(int) bool`)
diff, count = args.Diff([]interface{}{"string", 123, false})
assert.Contains(t, diff, `%!s(int=123) matched by func(int) bool`)
diff, count = args.Diff([]interface{}{"string", 123, true})
assert.Equal(t, 0, count)
assert.Contains(t, diff, `No differences.`)
}
func Test_Arguments_Assert(t *testing.T) {
var args Arguments = []interface{}{"string", 123, true}