diff --git a/mock/mock.go b/mock/mock.go index 7f4d28d5e..746151e1b 100644 --- a/mock/mock.go +++ b/mock/mock.go @@ -952,6 +952,18 @@ func (args Arguments) Is(objects ...interface{}) bool { return true } +// missingArgument is the sentinel [Arguments.Diff] uses for an argument that +// only one side has, either because the call passed fewer arguments than the +// expectation declares or because it passed more. The type is unexported and +// has no constructor, so an expectation can never hold a value equal to it. +type missingArgument struct{} + +// String returns the text used for a missing argument in failure messages. +func (missingArgument) String() string { return "(Missing)" } + +// missing is the only value of type [missingArgument]. +var missing missingArgument + // Diff gets a string describing the differences between the arguments // and the specified objects. // @@ -972,21 +984,33 @@ func (args Arguments) Diff(objects []interface{}) (string, int) { var actualFmt, expectedFmt string if len(objects) <= i { - actual = "(Missing)" - actualFmt = "(Missing)" + actual = missing + actualFmt = missing.String() } else { actual = objects[i] actualFmt = fmt.Sprintf("(%[1]T=%[1]v)", actual) } if len(args) <= i { - expected = "(Missing)" - expectedFmt = "(Missing)" + expected = missing + expectedFmt = missing.String() } else { expected = args[i] expectedFmt = fmt.Sprintf("(%[1]T=%[1]v)", expected) } + // A missing argument on either side means the call and the expectation + // disagree on how many arguments there are, which no matcher can + // reconcile. Report the difference before any matcher is given the + // sentinel to look at. + _, actualMissing := actual.(missingArgument) + _, expectedMissing := expected.(missingArgument) + if actualMissing || expectedMissing { + differences++ + output = fmt.Sprintf("%s\t%d: FAIL: %s != %s\n", output, i, actualFmt, expectedFmt) + continue + } + if matcher, ok := expected.(argumentMatcher); ok { var matches bool func() { diff --git a/mock/mock_test.go b/mock/mock_test.go index 3dc9e0b1e..c30f526c4 100644 --- a/mock/mock_test.go +++ b/mock/mock_test.go @@ -1932,6 +1932,89 @@ func Test_Arguments_Diff_DifferentNumberOfArgs(t *testing.T) { } +func Test_Arguments_Diff_MissingArgument_WithAnythingArgument(t *testing.T) { + t.Parallel() + + var args = Arguments([]interface{}{"string", Anything}) + diff, count := args.Diff([]interface{}{"string"}) + + assert.Equal(t, 1, count) + assert.Contains(t, diff, `(Missing) != (string=mock.Anything)`) +} + +func Test_Arguments_Diff_MissingArgument_WithAnythingOfTypeArgument(t *testing.T) { + t.Parallel() + + var args = Arguments([]interface{}{"string", AnythingOfType("string")}) + diff, count := args.Diff([]interface{}{"string"}) + + assert.Equal(t, 1, count) + assert.Contains(t, diff, `(Missing) != (mock.anythingOfTypeArgument=string)`) +} + +func Test_Arguments_Diff_MissingArgument_WithIsTypeArgument(t *testing.T) { + t.Parallel() + + var args = Arguments([]interface{}{"string", IsType("")}) + _, count := args.Diff([]interface{}{"string"}) + + assert.Equal(t, 1, count) +} + +func Test_Arguments_Diff_MissingArgument_WithArgMatcher(t *testing.T) { + t.Parallel() + + var called bool + matchFn := func(s string) bool { + called = true + return true + } + var args = Arguments([]interface{}{"string", MatchedBy(matchFn)}) + diff, count := args.Diff([]interface{}{"string"}) + + assert.Equal(t, 1, count) + assert.Contains(t, diff, `(Missing) != (mock.argumentMatcher=func(string) bool)`) + assert.False(t, called, "the matcher must not be called for an argument that was never passed") +} + +func Test_Arguments_Diff_MissingArgument_WithFunctionalOptionsArgument(t *testing.T) { + t.Parallel() + + var args = Arguments([]interface{}{"string", FunctionalOptions()}) + _, count := args.Diff([]interface{}{"string"}) + + assert.Equal(t, 1, count) +} + +func Test_Arguments_Diff_MissingArgument_WithLiteralMissingString(t *testing.T) { + t.Parallel() + + var args = Arguments([]interface{}{"string", "(Missing)"}) + diff, count := args.Diff([]interface{}{"string"}) + + assert.Equal(t, 1, count) + assert.Contains(t, diff, `(Missing) != (string=(Missing))`) +} + +func Test_Arguments_Diff_MissingExpectation_WithAnythingArgument(t *testing.T) { + t.Parallel() + + var args = Arguments([]interface{}{"string"}) + diff, count := args.Diff([]interface{}{"string", Anything}) + + assert.Equal(t, 1, count) + assert.Contains(t, diff, `(string=mock.Anything) != (Missing)`) +} + +func Test_Arguments_Diff_MissingArgument_StillMatchesWhenPresent(t *testing.T) { + t.Parallel() + + var args = Arguments([]interface{}{Anything, AnythingOfType("string"), IsType(0), MatchedBy(func(b bool) bool { return b })}) + _, count := args.Diff([]interface{}{"anything", "string", 123, true}) + + assert.Equal(t, 0, count) +} + func Test_Arguments_Diff_WithAnythingArgument(t *testing.T) { t.Parallel()