Repository: golang/mock
Pull request #236 (2018-11-29 22:38:01)

gives full control on both the Want and Got field formatting
- WantFormatter is used to modify a Matcher's String() method.
- GotFormatter is used to format the Got value for printing a failure message.

This gives full control on both the Want and Got field formatting.


Patch:
diff --git a/README.md b/README.md
index 3f588f8..3fd60db 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 be60e41..826f45d 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/matchers.go b/gomock/matchers.go
index 199b1e3..378c404 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 {


Test patch:
diff --git a/gomock/controller_test.go b/gomock/controller_test.go
index 0f1eb01..c0d42c8 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)