Skip to content

mock: add new AnythingImplementing function to check if values implement interfaces#1853

Open
GCrispino wants to merge 3 commits intostretchr:masterfrom
GCrispino:anything-implementing
Open

mock: add new AnythingImplementing function to check if values implement interfaces#1853
GCrispino wants to merge 3 commits intostretchr:masterfrom
GCrispino:anything-implementing

Conversation

@GCrispino
Copy link

Summary

The mock.AnythingImplementing function is added such that we can check if arguments used in mocks implemented a certain interface.

Example of usage (see the Motivation section for more background on why I think this is useful):

	var args = Arguments([]interface{}{AnythingImplementing((*context.Context)(nil))})
	args.Assert(t, AnythingImplementing(context.Background())

Changes

  • Addition of AnythingImplementing function
    • It uses the aux anythingImplementing private type for doing the type checks
  • Update in Diff function
    • a new case in Diff's switch expected := expected.(type) type switch to cover the use of AnythingImplementing. If a mismatch has been detected, the error format string %s\t%d: FAIL: value of type %T does not implement interface %s\n is included in the final error string
  • Tests
    • Test_Mock_AssertCalled_WithAnythingImplementingArgument: tests successful case
    • Test_Arguments_Diff_WithAnythingImplementingArgument: tests the use of Diff where no mismatch has been detected
    • Test_Arguments_Diff_WithAnythingImplementingArgument_Failing: tests the format of the error string in the case that
      a mismatch has been detected in Diff

Motivation

If we want to mock a function that takes a value of a certain interface type, to the best of my knowledge, there's nothing in testify for checking if an argument implements a certain interface.
As far as I know, the closest is AnythingOfType, but then we need to know which concrete type is being used as argument to the function to use it.

Example

Consider the following example of the Caller interface, which only has a function Call that takes a value of the context.Context interface type:

// Caller is a simple interface with a Call method that taksa a context and returns an error.
type Caller interface {
	Call(context.Context) error
}

// MustCall calls the Call method of the provided caller.
func MustCall(c Caller) {
	ctx := context.Background()
	if err := c.Call(ctx); err != nil {
		panic(err)
	}
}

When testing it using mocks and mock.AnythingOfType, we need to know the concrete type of the argument being received by Call, doing something like the following:

type MockCaller struct {
	mock.Mock
}

func (c *MockCaller) Call(ctx context.Context) error {
	args := c.Called(ctx)
	return args.Error(0)
}

func TestMustCall(t *testing.T) {
	m := MockCaller{}

	m.On("Call", mock.AnythingOfType("context.backgroundCtx")).
		Return(nil)

	MustCall(&m)

	m.AssertExpectations(t)
}

We need to know that MustCall uses context.backgroundCtx, which can be a bit fragile. For example, MustCall could use different concrete types depending on some logic, which would make checking the mocks a bit more brittle.

With mock.AnythingImplementing, we can just check against the context.Context interface, which is cleaner:

func TestMustCallAnythingImplementing(t *testing.T) {
	m := MockCaller{}

	m.On("Call", mock.AnythingImplementing((*context.Context)(nil))).
		Return(nil)

	MustCall(&m)

	m.AssertExpectations(t)
}

Checking against a type that is not implemented by the argument provided in the MustCall function will make the test fail:

// fails ❌, as context.Background used inside MustCall doesn't implement testInterface
func TestMustCallAnythingImplementingFails(t *testing.T) {
	m := MockCaller{}

	m.On("Call", mock.AnythingImplementing((*testInterface)(nil))).
		Return(nil)

	MustCall(&m)

	m.AssertExpectations(t)
}

This is the failure shown in the test:


--- FAIL: TestMustCallAnythingImplementingFails (0.00s)
panic:

        mock: Unexpected Method Call
        -----------------------------

        Call(context.backgroundCtx)
                        0: context.backgroundCtx{emptyCtx:context.emptyCtx{}}

        The closest call I have is:

        Call(mock.anythingImplementing)
                        0: mock.anythingImplementing{interfaceType:(*reflect.rtype)(0x103031840)}


        Diff: 0: FAIL: value of type context.backgroundCtx does not implement interface test.testInterface
        at: [/Users/gabriel/code/Go/test-testify-mockery/test_test.go:15 /Users/gabriel/code/Go/test-testify-mockery/test.go:15 /Users/gabriel/code/Go/test-testify-mockery/test_test.go:53]
         [recovered]
        panic:

        mock: Unexpected Method Call
        -----------------------------

        Call(context.backgroundCtx)
                        0: context.backgroundCtx{emptyCtx:context.emptyCtx{}}

        The closest call I have is:

        Call(mock.anythingImplementing)
                        0: mock.anythingImplementing{interfaceType:(*reflect.rtype)(0x103031840)}


        Diff: 0: FAIL: value of type context.backgroundCtx does not implement interface test.testInterface
        at: [/Users/gabriel/code/Go/test-testify-mockery/test_test.go:15 /Users/gabriel/code/Go/test-testify-mockery/test.go:15 /Users/gabriel/code/Go/test-testify-mockery/test_test.go:53]

@dolmen dolmen added pkg-mock Any issues related to Mock enhancement: extend API An enhancement that grows the API surface labels Feb 23, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement: extend API An enhancement that grows the API surface pkg-mock Any issues related to Mock

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants