Closed liamylian closed 4 years ago
Hey @liamylian would you mind providing me a more concrete example code snippets?
Closing due to lack of response. If you want to submit an example and reopen, feel free.
I'm assuming what @liamylian meant, is that the generated code uses empty interfaces as argument types for the MockRecorder methods. Compare the argument types in the Mock and MockRecorder methods:
// GetUserRoles mocks base method
func (m *MockRepository) GetUserRoles(arg0 context.Context, arg1 string) ([]*role.UserRole, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "GetUserRoles", arg0, arg1)
ret0, _ := ret[0].([]*role.UserRole)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// GetUserRoles indicates an expected call of GetUserRoles
func (mr *MockRepositoryMockRecorder) GetUserRoles(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetUserRoles", reflect.TypeOf((*MockRepository)(nil).GetUserRoles), arg0, arg1)
}
In the MockRecorder methods, both arg0 and arg1 are empty interfaces, but it would be nice to have them as (arg0 context.Context, arg1 string)
as in the real method (first method in the example).
Is there a specific reason for this? I'm assuming there probably is, but if not, I could take a look at submitting a PR.
Hi @codyoss, any thoughts on what I commented above? Still willing to provide a PR if you think this is something that makes sense, and which has a good chance of getting merged!
@jcxplorer Recorders can accept matchers is the reason. Example:
m.EXPECT(). GetUserRoles(gomock.Any(), gomock.Any()).Return(nil, nil)
Now that go has support for generics, I think it would be possible to do this. For example, here's what I've prototyped out:
type CallOf[Params any, Return any] struct {
t gomock.TestHelper
*gomock.Call
}
func (c *CallOf[Params, Return]) Return(ret Return) *CallOf[Params, Return] {
c.t.Helper()
val := reflect.ValueOf(ret)
var rets []any
for i := 0; i < val.NumField(); i++ {
field := val.Field(i).Interface()
rets = append(rets, field)
}
c.Call.Return(rets...)
return c
}
func (c *CallOf[Params, Return]) DoAndReturn(fn func(params Params) Return) *CallOf[Params, Return] {
c.t.Helper()
c.Call.DoAndReturn(fn)
return c
}
type MatcherOf[T any] interface {
Matches(actual T) bool
fmt.Stringer
}
func ToGomock[T any](matcher MatcherOf[T]) gomock.Matcher {
return &gomockMatcherOf[T]{matcher: matcher}
}
type gomockMatcherOf[T any] struct {
matcher MatcherOf[T]
}
func (g *gomockMatcherOf[T]) Matches(x interface{}) bool {
if val, ok := x.(T); ok {
return g.matcher.Matches(val)
}
return false
}
func (g *gomockMatcherOf[T]) String() string {
return g.matcher.String()
}
// Acquire indicates an expected call of Acquire.
func (mr *MockDistributedLockRepoMockRecorder) Acquire(ctx MatcherOf[context.Context], key MatcherOf[string]) *CallOf[
struct {
ctx MatcherOf[context.Context]
key MatcherOf[string]
}, struct {
r1 *v4.Mutex
r2 error
}] {
mr.mock.ctrl.T.Helper()
call := mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Acquire", reflect.TypeOf((*MockDistributedLockRepo)(nil).Acquire), ToGomock(ctx), ToGomock(key))
return &CallOf[
struct {
ctx MatcherOf[context.Context]
key MatcherOf[string]
}, struct {
r1 *v4.Mutex
r2 error
}]{
call,
}
}```
This is a bit cumbersome since it wraps params and return values in structs (due to lack of variadic generics), but we could also have some code-genned tuple structs that would eliminate the wrapping.
It would be nice to mock method with statically typed arguments, rather than using interface arguments. I find myself check argument types all the time when mock a method, that is annoying and inefficent.