I have the following method in my RedisClient:
```go
func (r *RedisClient) GetStruct(ctx context.Context, key Key, value interface{}) (*time.Duration, error) {
// stuff happens here
}
And the corresponding interface and mock:
type Client interface {
GetStruct(ctx context.Context, key Key, value interface{}) (*time.Duration, error)
}
I want to test this method using GoMock’s DoAndReturn and assign a value to the value argument. My current attempt:
s.mockRedisClient.EXPECT().
GetStruct(gomock.Any(), cache.BadgesAll, gomock.AssignableToTypeOf(&models.Badge{})).
DoAndR
eturn(func(ctx context.Context, key cache.Key, value any) (*time.Duration, error) {
value = []*models.Thing{{ID: 1}}
duration := time.Minute * 1
return &duration, nil
})
````Preformatted text`
However, back in the controller I’m testing, value is always nil. What am I missing here, and how can I properly assign a value to the interface argument using DoAndReturn?
In Go, assigning to the argument inside your DoAndReturn function doesn’t change the caller’s variable because interface{} is passed by value. You need to cast it to the actual pointer type and modify it directly:
s.mockRedisClient.EXPECT().
GetStruct(gomock.Any(), cache.BadgesAll, gomock.AssignableToTypeOf(&models.Badge{})).
DoAndReturn`(func(`ctx c``ontext.Context`, key `cache.K`ey, value interface{}) (*time.Duration, error) {
if v, ok := value.(*[]*`models.Thing`); ok {
*v = []*`models.Thing`{{ID: 1}}
}
duration := time.Minute
return &duration, nil
})
This way, the caller sees the updated value because you’re dereferencing the pointer that was passed in.
I ran into this issue before: in Go, even though the argument is an interface{}, it wraps a copy of the pointer, not the original variable. So value = … just reassigns the local copy. Using a type assertion and dereferencing works reliably:
DoAndReturn(func(ctx context.Context, key cache.Key, value interface{}) (*time.Duration, error) {
things := value.(*[]*models.Thing)
*things = append(*things, &models.Thing{ID: 1})
d := time.Minute
return &d, nil
})
It feels subtle, but once you understand Go’s interface semantics, it clicks.
Another approach I often use is defining the interface argument as a pointer to a concrete type in the call site. Then your DoAndReturn can safely mutate it without type assertion magic:
var badges []*models.Thing
s.mockRedisClient.EXPECT().
GetStruct(gomock.Any(), cache.BadgesAll, &badges).
DoAndReturn(func(ctx context.Context, key cache.Key, value interface{}) (*time.Duration, error) {
*value.(*[]*models.Thing) = []*models.Thing{{ID: 1}}
d := time.Minute
return &d, nil
})
This makes tests cleaner and avoids the common mistake of thinking value = … would update the caller.