Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 28 additions & 4 deletions mock/mock.go
Original file line number Diff line number Diff line change
Expand Up @@ -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.
//
Expand All @@ -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() {
Expand Down
83 changes: 83 additions & 0 deletions mock/mock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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()

Expand Down