diff --git a/README.md b/README.md index 3f588f80..3fd60db0 100644 --- a/README.md +++ b/README.md @@ -166,6 +166,53 @@ func TestFoo(t *testing.T) { } ``` +### Modifying Failure Messages + +When a matcher reports a failure, it prints the received (`Got`) vs the +expected (`Want`) value. + +``` +Got: [3] +Want: is equal to 2 +Expected call at user_test.go:33 doesn't match the argument at index 1. +Got: [0 1 1 2 3] +Want: is equal to 1 +``` + +##### Modifying `Want` + +The `Want` value comes from the matcher's `String()` method. If the matcher's +default output doesn't meet your needs, then it can be modified as follows: + +```go +gomock.WantFormatter( + gomock.StringerFunc(func() string { return "is equal to fifteen" }), + gomock.Eq(15), +) +``` + +This modifies the `gomock.Eq(15)` matcher's output for `Want:` from `is equal +to 15` to `is equal to fifteen`. + +##### Modifying `Got` + +The `Got` value comes from the object's `String()` method if it is available. +In some cases the output of an object is difficult to read (e.g., `[]byte`) and +it would be helpful for the test to print it differently. The following +modifies how the `Got` value is formatted: + +```go +gomock.GotFormatterAdapter( + gomock.GotFormatterFunc(func(i interface{}) string { + // Leading 0s + return fmt.Sprintf("%02d", i) + }), + gomock.Eq(15), +) +``` + +If the received value is `3`, then it will be printed as `03`. + [golang]: http://golang.org/ [golang-install]: http://golang.org/doc/install.html#releases [gomock-ref]: http://godoc.org/github.com/golang/mock/gomock diff --git a/gomock/call.go b/gomock/call.go index be60e413..826f45d2 100644 --- a/gomock/call.go +++ b/gomock/call.go @@ -301,8 +301,15 @@ func (c *Call) matches(args []interface{}) error { for i, m := range c.args { if !m.Matches(args[i]) { - return fmt.Errorf("Expected call at %s doesn't match the argument at index %s.\nGot: %v\nWant: %v", - c.origin, strconv.Itoa(i), args[i], m) + got := fmt.Sprintf("%v", args[i]) + if gs, ok := m.(GotFormatter); ok { + got = gs.Got(args[i]) + } + + return fmt.Errorf( + "Expected call at %s doesn't match the argument at index %d.\nGot: %v\nWant: %v", + c.origin, i, got, m, + ) } } } else { diff --git a/gomock/controller_test.go b/gomock/controller_test.go index 0f1eb01f..c0d42c86 100644 --- a/gomock/controller_test.go +++ b/gomock/controller_test.go @@ -317,6 +317,63 @@ func TestUnexpectedArgValue_SecondArg(t *testing.T) { }) } +func TestUnexpectedArgValue_WantFormatter(t *testing.T) { + reporter, ctrl := createFixtures(t) + defer reporter.recoverUnexpectedFatal() + subject := new(Subject) + + expectedArg0 := TestStruct{Number: 123, Message: "hello"} + ctrl.RecordCall( + subject, + "ActOnTestStructMethod", + expectedArg0, + gomock.WantFormatter( + gomock.StringerFunc(func() string { return "is equal to fifteen" }), + gomock.Eq(15), + ), + ) + + reporter.assertFatal(func() { + ctrl.Call(subject, "ActOnTestStructMethod", TestStruct{Number: 123, Message: "hello"}, 3) + }, "Unexpected call to", "doesn't match the argument at index 1", + "Got: 3\nWant: is equal to fifteen") + + reporter.assertFatal(func() { + // The expected call wasn't made. + ctrl.Finish() + }) +} + +func TestUnexpectedArgValue_GotFormatter(t *testing.T) { + reporter, ctrl := createFixtures(t) + defer reporter.recoverUnexpectedFatal() + subject := new(Subject) + + expectedArg0 := TestStruct{Number: 123, Message: "hello"} + ctrl.RecordCall( + subject, + "ActOnTestStructMethod", + expectedArg0, + gomock.GotFormatterAdapter( + gomock.GotFormatterFunc(func(i interface{}) string { + // Leading 0s + return fmt.Sprintf("%02d", i) + }), + gomock.Eq(15), + ), + ) + + reporter.assertFatal(func() { + ctrl.Call(subject, "ActOnTestStructMethod", TestStruct{Number: 123, Message: "hello"}, 3) + }, "Unexpected call to", "doesn't match the argument at index 1", + "Got: 03\nWant: is equal to 15") + + reporter.assertFatal(func() { + // The expected call wasn't made. + ctrl.Finish() + }) +} + func TestAnyTimes(t *testing.T) { reporter, ctrl := createFixtures(t) subject := new(Subject) diff --git a/gomock/matchers.go b/gomock/matchers.go index 199b1e37..378c4046 100644 --- a/gomock/matchers.go +++ b/gomock/matchers.go @@ -29,6 +29,63 @@ type Matcher interface { String() string } +// WantFormatter modifies the given Matcher's String() method to the given +// Stringer. This allows for control on how the "Want" is formatted when +// printing . +func WantFormatter(s fmt.Stringer, m Matcher) Matcher { + type matcher interface { + Matches(x interface{}) bool + } + + return struct { + matcher + fmt.Stringer + }{ + matcher: m, + Stringer: s, + } +} + +// StringerFunc type is an adapter to allow the use of ordinary functions as +// a Stringer. If f is a function with the appropriate signature, +// StringerFunc(f) is a Stringer that calls f. +type StringerFunc func() string + +// String implements fmt.Stringer. +func (f StringerFunc) String() string { + return f() +} + +// GotFormatter is used to better print failure messages. If a matcher +// implements GotFormatter, it will use the result from Got when printing +// the failure message. +type GotFormatter interface { + // Got is invoked with the received value. The result is used when + // printing the failure message. + Got(got interface{}) string +} + +// GotFormatterFunc type is an adapter to allow the use of ordinary +// functions as a GotFormatter. If f is a function with the appropriate +// signature, GotFormatterFunc(f) is a GotFormatter that calls f. +type GotFormatterFunc func(got interface{}) string + +// Got implements GotFormatter. +func (f GotFormatterFunc) Got(got interface{}) string { + return f(got) +} + +// GotFormatterAdapter attaches a GotFormatter to a Matcher. +func GotFormatterAdapter(s GotFormatter, m Matcher) Matcher { + return struct { + GotFormatter + Matcher + }{ + GotFormatter: s, + Matcher: m, + } +} + type anyMatcher struct{} func (anyMatcher) Matches(x interface{}) bool {