testify
testify copied to clipboard
Support for Variadic Methods in Mocks without modifying mock structure
Story:
As a mock implementer, I have several methods with variadic arg signature. Currently we can get around this problem by removing the v... from the underlying mock item (the result of the unroll variadic flag with mockery).
I have read the documentation found:
- https://github.com/stretchr/testify/issues/101
- https://github.com/stretchr/testify/issues/897
- https://github.com/stretchr/testify/issues/1148
Here is ALL the code to replicate on your local development machine:
Target Variadic Interface
type TestStructure interface {
VariadicTest(startArg string, v ...interface{})
}
Original Mock object from mockery
// IncorrectTestStructure is an autogenerated mock type for the IncorrectTestStructure type
type IncorrectTestStructure struct {
mock.Mock
}
// VariadicTest provides a mock function with a variadic method signature
func (_m *IncorrectTestStructure) VariadicTest(startArg string, v ...interface{}) {
var _ca []interface{}
_ca = append(_ca, startArg)
_ca = append(_ca, v...)
_m.Called(_ca...)
}
type mockConstructorTestingTNewIncorrectTestStructure interface {
mock.TestingT
Cleanup(func())
}
// NewIncorrectTestStructure creates a new instance of CorrectedTestStructure.
// It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewIncorrectTestStructure(t mockConstructorTestingTNewIncorrectTestStructure) *IncorrectTestStructure {
mock := &IncorrectTestStructure{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
Corrected Mock object from mockery
// CorrectedTestStructure is an autogenerated mock type for the CorrectedTestStructure type
type CorrectedTestStructure struct {
mock.Mock
}
// VariadicTest provides a mock function with a variadic method signature
func (_m *CorrectedTestStructure) VariadicTest(startArg string, v ...interface{}) {
var _ca []interface{}
_ca = append(_ca, startArg)
_ca = append(_ca, v)
_m.Called(_ca...)
}
type mockConstructorTestingTNewCorrectedTestStructure interface {
mock.TestingT
Cleanup(func())
}
// NewCorrectedTestStructure creates a new instance of CorrectedTestStructure.
// It also registers a testing interface on the mock and a cleanup function to assert the mocks expectations.
func NewCorrectedTestStructure(t mockConstructorTestingTNewCorrectedTestStructure) *CorrectedTestStructure {
mock := &CorrectedTestStructure{}
mock.Mock.Test(t)
t.Cleanup(func() { mock.AssertExpectations(t) })
return mock
}
A simple parent structure
type ParentStructure struct {
childObject TestStructure
}
func (m ParentStructure) VariadicTest(startArg string, v ...interface{}) {
m.childObject.VariadicTest(startArg,v...)
}
Test showing Incorrect Structure from mockery failing
func TestMinimumReplication_IncorrectTestStructure_Simple_Fails(t *testing.T) {
tests := []struct {
name string
mockArguments []interface{}
providedArguments []interface{}
}{
{name: "1 +1 Variadic Provided", mockArguments: []interface{}{mock.Anything,mock.Anything},providedArguments: []interface{}{"tester"} },
{name: "1 +2 Variadic Provided", mockArguments: []interface{}{mock.Anything,mock.Anything},providedArguments: []interface{}{"tester", "Test 2"} },
{name: "1 +3 Variadic Provided", mockArguments: []interface{}{mock.Anything,mock.Anything},providedArguments: []interface{}{"tester", "Test 2", "Test 3"} },
}
for _, tt := range tests {
mockedObject := new(IncorrectTestStructure)
mockedObject.On("VariadicTest",tt.mockArguments...).Return()
testTarget := ParentStructure{childObject: mockedObject}
testTarget.VariadicTest("Test call",tt.providedArguments...)
}
}
func TestMinimumReplication_IncorrectTestStructure_MatchedBy_Fails(t *testing.T) {
tests := []struct {
name string
providedArguments []interface{}
}{
{name: "1 +1 Variadic Provided", providedArguments: []interface{}{"tester"} },
{name: "1 +2 Variadic Provided", providedArguments: []interface{}{"tester", "Test 2"} },
{name: "1 +3 Variadic Provided", providedArguments: []interface{}{"tester", "Test 2", "Test 3"} },
}
for _, tt := range tests {
mockedObject := new(IncorrectTestStructure)
mockedObject.On("VariadicTest",mock.Anything, mock.MatchedBy(func(args []interface{}) bool {
return len(args) >= 1
}))
testTarget := ParentStructure{childObject: mockedObject}
testTarget.VariadicTest("Test call",tt.providedArguments...)
}
}
Test showing Corrected Structure from mockery SUCCEEDING
func TestMinimumReplication_CorrectedTestStructure_Simple_Succeeds(t *testing.T) {
tests := []struct {
name string
mockArguments []interface{}
providedArguments []interface{}
}{
{name: "1 +1 Variadic Provided", mockArguments: []interface{}{mock.Anything,mock.Anything},providedArguments: []interface{}{"tester"} },
{name: "1 +2 Variadic Provided", mockArguments: []interface{}{mock.Anything,mock.Anything},providedArguments: []interface{}{"tester", "Test 2"} },
{name: "1 +3 Variadic Provided", mockArguments: []interface{}{mock.Anything,mock.Anything},providedArguments: []interface{}{"tester", "Test 2", "Test 3"} },
}
for _, tt := range tests {
mockedObject := new(CorrectedTestStructure)
mockedObject.On("VariadicTest",tt.mockArguments...).Return()
testTarget := ParentStructure{childObject: mockedObject}
testTarget.VariadicTest("Test call",tt.providedArguments...)
}
}
func TestMinimumReplication_CorrectedTestStructure_MatchedBy_Succeeds(t *testing.T) {
tests := []struct {
name string
providedArguments []interface{}
}{
{name: "1 +1 Variadic Provided", providedArguments: []interface{}{"tester"} },
{name: "1 +2 Variadic Provided", providedArguments: []interface{}{"tester", "Test 2"} },
{name: "1 +3 Variadic Provided", providedArguments: []interface{}{"tester", "Test 2", "Test 3"} },
}
for _, tt := range tests {
mockedObject := new(CorrectedTestStructure)
mockedObject.On("VariadicTest",mock.Anything, mock.MatchedBy(func(args []interface{}) bool {
return len(args) >= 1
}))
testTarget := ParentStructure{childObject: mockedObject}
testTarget.VariadicTest("Test call",tt.providedArguments...)
}
}
Based off this information, it appears that we are currently using a patch solution as opposed to updating the underlying method. It looks like this could be resolved as part of here: https://github.com/stretchr/testify/blob/181cea6eab8b2de7071383eca4be32a424db38dd/mock/mock.go#L332
or here:
func (m *Mock) findExpectedCall(method string, arguments ...interface{}) (int, *Call) {
var expectedCall *Call
for i, call := range m.ExpectedCalls {
if call.Method == method {
_, diffCount := call.Arguments.Diff(arguments)
if diffCount == 0 {
expectedCall = call
if call.Repeatability > -1 {
return i, call
}
}
}
}
return -1, expectedCall
}
In the case of a variadic signature, we can compare the final argument definition to determine if the method is variadic.
In the case of methods, we can directly check via reflecting into the targetMethod (of type reflect.Method) and looking here: targetMethod.Func.Type().IsVariadic()
If we don't have access to that piece of information, we can check the Name + Kind(). This will come out as nil name, and of slice type.
Using the previous example, we can verify the accessibility via the following test (using the previously defined test structure)
Test Method for identifying variadic position
func Test_MethodRead(t *testing.T) {
ex := ParentStructure{}
target := reflect.TypeOf(ex)
t.Log("Methods")
for i := 0; i<target.NumMethod(); i++ {
targetMethod := target.Method(i)
targetType := targetMethod.Func.Type()
t.Logf("%s : Is Variadic? : %v",targetMethod.Name,targetType.IsVariadic())
methodType := targetMethod.Type
if (targetType.IsVariadic()) {
parameterIndex := methodType.NumIn() -1
targetInputArg := methodType.In(parameterIndex)
t.Logf("%s : %s",targetMethod.Name,targetInputArg.Name())
t.Logf("%s : %s : %v",targetMethod.Name,targetInputArg.Name(),targetInputArg.Kind())
t.Logf("%s : %s : Type : %s",targetMethod.Name,targetInputArg.Name(),targetInputArg.String())
}
}
t.FailNow() // Forces logs to display
}
Personally, I think the implementation of:
mockedObject.On("VariadicTest",mock.Anything,mock.Variadic(mock.Anything)).Return() would make sense based on the current implementation.
An alternative could be a new method which does (or does not) check arguments
mockedObject.OnAllCalls("VariadicTest").Return()
edit: minor update due to incorrect label on summary: "Test showing Corrected Structure from mockery SUCCEEDING"
Any updates guys? 😢
Are there any plans to implement this?