Repository: stretchr/testify
Pull request #1074 (2021-05-04 15:53:09)

Improve mock.MatchedBy failed comparison Diff message
## Summary
Improve the Diff message for `mock.MatchedBy` functions taking a reference type.

## Changes
The `argumentMatcher.String` method internally now uses the `reflect.Type.String` method instead of the `reflect.Type.Name` method.

## Motivation
Prior to this change a `mock.MatchedBy` function taking a reference type, such as a slice (`func([]int) bool`), would have the Diff presented as `... func() bool`; now the Diff will accurately be presented as `... func([]int) bool`.

## Related issues
N/A



Patch:
diff --git a/.travis.gofmt.sh b/.ci.gofmt.sh
similarity index 100%
rename from .travis.gofmt.sh
rename to .ci.gofmt.sh
diff --git a/.travis.gogenerate.sh b/.ci.gogenerate.sh
similarity index 100%
rename from .travis.gogenerate.sh
rename to .ci.gogenerate.sh
diff --git a/.travis.govet.sh b/.ci.govet.sh
similarity index 100%
rename from .travis.govet.sh
rename to .ci.govet.sh
diff --git a/.github/dependabot.yml b/.github/dependabot.yml
new file mode 100644
index 0000000..bf6944e
--- /dev/null
+++ b/.github/dependabot.yml
@@ -0,0 +1,10 @@
+version: 2
+updates:
+- package-ecosystem: gomod
+  directory: /
+  schedule:
+    interval: daily
+- package-ecosystem: github-actions
+  directory: /
+  schedule:
+    interval: daily
diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml
new file mode 100644
index 0000000..f432502
--- /dev/null
+++ b/.github/workflows/main.yml
@@ -0,0 +1,19 @@
+name: All builds
+on: [push, pull_request]
+
+jobs:
+  build:
+    runs-on: ubuntu-latest
+    strategy:
+      matrix:
+        go_version: ["1.16.5", "1.15.13", "1.14.15"]
+    steps:
+      - uses: actions/checkout@v2
+      - name: Setup Go
+        uses: actions/setup-go@v2
+        with:
+          go-version: ${{ matrix.go_version }}
+      - run: ./.ci.gogenerate.sh    
+      - run: ./.ci.gofmt.sh
+      - run: ./.ci.govet.sh
+      - run: go test -v -race ./...
diff --git a/assert/assertions.go b/assert/assertions.go
index 6882692..30742a0 100644
--- a/assert/assertions.go
+++ b/assert/assertions.go
@@ -1164,6 +1164,10 @@ func InDelta(t TestingT, expected, actual interface{}, delta float64, msgAndArgs
 		return Fail(t, fmt.Sprintf("Parameters must be numerical"), msgAndArgs...)
 	}
 
+	if math.IsNaN(af) && math.IsNaN(bf) {
+		return true
+	}
+
 	if math.IsNaN(af) {
 		return Fail(t, fmt.Sprintf("Expected must not be NaN"), msgAndArgs...)
 	}
@@ -1250,8 +1254,12 @@ func InDeltaMapValues(t TestingT, expected, actual interface{}, delta float64, m
 
 func calcRelativeError(expected, actual interface{}) (float64, error) {
 	af, aok := toFloat(expected)
-	if !aok {
-		return 0, fmt.Errorf("expected value %q cannot be converted to float", expected)
+	bf, bok := toFloat(actual)
+	if !aok || !bok {
+		return 0, fmt.Errorf("Parameters must be numerical")
+	}
+	if math.IsNaN(af) && math.IsNaN(bf) {
+		return 0, nil
 	}
 	if math.IsNaN(af) {
 		return 0, errors.New("expected value must not be NaN")
@@ -1259,10 +1267,6 @@ func calcRelativeError(expected, actual interface{}) (float64, error) {
 	if af == 0 {
 		return 0, fmt.Errorf("expected value must have a value other than zero to calculate the relative error")
 	}
-	bf, bok := toFloat(actual)
-	if !bok {
-		return 0, fmt.Errorf("actual value %q cannot be converted to float", actual)
-	}
 	if math.IsNaN(bf) {
 		return 0, errors.New("actual value must not be NaN")
 	}
diff --git a/mock/mock.go b/mock/mock.go
index 5d445c6..853da6c 100644
--- a/mock/mock.go
+++ b/mock/mock.go
@@ -728,7 +728,7 @@ func (f argumentMatcher) Matches(argument interface{}) bool {
 }
 
 func (f argumentMatcher) String() string {
-	return fmt.Sprintf("func(%s) bool", f.fn.Type().In(0).Name())
+	return fmt.Sprintf("func(%s) bool", f.fn.Type().In(0).String())
 }
 
 // MatchedBy can be used to match a mock call based on only certain properties


Test patch:
diff --git a/assert/assertions_test.go b/assert/assertions_test.go
index 1350567..4fafb51 100644
--- a/assert/assertions_test.go
+++ b/assert/assertions_test.go
@@ -1324,6 +1324,7 @@ func TestInDelta(t *testing.T) {
 	False(t, InDelta(mockT, "", nil, 1), "Expected non numerals to fail")
 	False(t, InDelta(mockT, 42, math.NaN(), 0.01), "Expected NaN for actual to fail")
 	False(t, InDelta(mockT, math.NaN(), 42, 0.01), "Expected NaN for expected to fail")
+	True(t, InDelta(mockT, math.NaN(), math.NaN(), 0.01), "Expected NaN for both to pass")
 
 	cases := []struct {
 		a, b  interface{}
@@ -1354,19 +1355,19 @@ func TestInDeltaSlice(t *testing.T) {
 	mockT := new(testing.T)
 
 	True(t, InDeltaSlice(mockT,
-		[]float64{1.001, 0.999},
-		[]float64{1, 1},
-		0.1), "{1.001, 0.009} is element-wise close to {1, 1} in delta=0.1")
+		[]float64{1.001, math.NaN(), 0.999},
+		[]float64{1, math.NaN(), 1},
+		0.1), "{1.001, NaN, 0.009} is element-wise close to {1, NaN, 1} in delta=0.1")
 
 	True(t, InDeltaSlice(mockT,
-		[]float64{1, 2},
-		[]float64{0, 3},
-		1), "{1, 2} is element-wise close to {0, 3} in delta=1")
+		[]float64{1, math.NaN(), 2},
+		[]float64{0, math.NaN(), 3},
+		1), "{1, NaN, 2} is element-wise close to {0, NaN, 3} in delta=1")
 
 	False(t, InDeltaSlice(mockT,
-		[]float64{1, 2},
-		[]float64{0, 3},
-		0.1), "{1, 2} is not element-wise close to {0, 3} in delta=0.1")
+		[]float64{1, math.NaN(), 2},
+		[]float64{0, math.NaN(), 3},
+		0.1), "{1, NaN, 2} is not element-wise close to {0, NaN, 3} in delta=0.1")
 
 	False(t, InDeltaSlice(mockT, "", nil, 1), "Expected non numeral slices to fail")
 }
@@ -1386,10 +1387,12 @@ func TestInDeltaMapValues(t *testing.T) {
 			expect: map[string]float64{
 				"foo": 1.0,
 				"bar": 2.0,
+				"baz": math.NaN(),
 			},
 			actual: map[string]float64{
 				"foo": 1.01,
 				"bar": 1.99,
+				"baz": math.NaN(),
 			},
 			delta: 0.1,
 			f:     True,
@@ -1422,10 +1425,10 @@ func TestInDeltaMapValues(t *testing.T) {
 		{
 			title: "Within delta with zero value",
 			expect: map[string]float64{
-				"zero": 0.0,
+				"zero": 0,
 			},
 			actual: map[string]float64{
-				"zero": 0.0,
+				"zero": 0,
 			},
 			delta: 0.1,
 			f:     True,
@@ -1433,12 +1436,12 @@ func TestInDeltaMapValues(t *testing.T) {
 		{
 			title: "With missing key with zero value",
 			expect: map[string]float64{
-				"zero": 0.0,
-				"foo":  0.0,
+				"zero": 0,
+				"foo":  0,
 			},
 			actual: map[string]float64{
-				"zero": 0.0,
-				"bar":  0.0,
+				"zero": 0,
+				"bar":  0,
 			},
 			f: False,
 		},
@@ -1462,6 +1465,7 @@ func TestInEpsilon(t *testing.T) {
 		{uint64(100), uint8(101), 0.01},
 		{0.1, -0.1, 2},
 		{0.1, 0, 2},
+		{math.NaN(), math.NaN(), 1},
 		{time.Second, time.Second + time.Millisecond, 0.002},
 	}
 
@@ -1497,9 +1501,9 @@ func TestInEpsilonSlice(t *testing.T) {
 	mockT := new(testing.T)
 
 	True(t, InEpsilonSlice(mockT,
-		[]float64{2.2, 2.0},
-		[]float64{2.1, 2.1},
-		0.06), "{2.2, 2.0} is element-wise close to {2.1, 2.1} in espilon=0.06")
+		[]float64{2.2, math.NaN(), 2.0},
+		[]float64{2.1, math.NaN(), 2.1},
+		0.06), "{2.2, NaN, 2.0} is element-wise close to {2.1, NaN, 2.1} in espilon=0.06")
 
 	False(t, InEpsilonSlice(mockT,
 		[]float64{2.2, 2.0},
diff --git a/mock/mock_test.go b/mock/mock_test.go
index 097addc..91828f8 100644
--- a/mock/mock_test.go
+++ b/mock/mock_test.go
@@ -1482,6 +1482,10 @@ func (s *timer) GetTime(i int) string {
 	return s.Called(i).Get(0).(string)
 }
 
+func (s *timer) GetTimes(times []int) string {
+	return s.Called(times).Get(0).(string)
+}
+
 type tCustomLogger struct {
 	*testing.T
 	logs []string
@@ -1554,6 +1558,23 @@ func TestArgumentMatcherToPrintMismatch(t *testing.T) {
 	m.AssertExpectations(t)
 }
 
+func TestArgumentMatcherToPrintMismatchWithReferenceType(t *testing.T) {
+	defer func() {
+		if r := recover(); r != nil {
+			matchingExp := regexp.MustCompile(
+				`\s+mock: Unexpected Method Call\s+-*\s+GetTimes\(\[\]int\)\s+0: \[\]int\{1\}\s+The closest call I have is:\s+GetTimes\(mock.argumentMatcher\)\s+0: mock.argumentMatcher\{.*?\}\s+Diff:.*\(\[\]int=\[1\]\) not matched by func\(\[\]int\) bool`)
+			assert.Regexp(t, matchingExp, r)
+		}
+	}()
+
+	m := new(timer)
+	m.On("GetTimes", MatchedBy(func(_ []int) bool { return false })).Return("SomeTime").Once()
+
+	res := m.GetTimes([]int{1})
+	require.Equal(t, "SomeTime", res)
+	m.AssertExpectations(t)
+}
+
 func TestClosestCallMismatchedArgumentInformationShowsTheClosest(t *testing.T) {
 	defer func() {
 		if r := recover(); r != nil {