我有以下方法:
func (r *RedisClient) GetStruct(ctx context.Context, key Key, value interface{}) (*time.Duration, error) {
// stuff happens here
}
这个方法有一个接口(和相应的mock):
type Client interface {
GetStruct(ctx context.Context, key Key, value interface{}) (*time.Duration, error)
}
我正在尝试使用 DoAndReturn 测试该方法,只是我在为
value
赋值时遇到了问题
我正在做以下事情:
s.mockRedisClient.EXPECT().GetStruct(gomock.Any(), cache.BadgesAll, gomock.AssignableToTypeOf(&models.Badge{})).DoAndReturn(func(ctx context.Context, key cache.Key, value any) (*time.Duration, error) {
value = []*models.Thing{{ID: 1}}
duration := time.Minute * 1
return &duration, nil
})
仅回到我正在测试的控制器中,
value
始终为零。有人能指出我在这里缺少什么吗?谢谢
是的,所以有几件事需要考虑
仅回到我正在测试的控制器中,值始终为零。有人能指出我在这里缺少什么吗?
这个语句可以用几种方式来解读:要么
value
总是 nil
,因为你正在测试的代码就是这种情况,在这种情况下,除了检查 nil 之外,你对该参数不感兴趣。因此,您可以像这样设置您的模拟(技术上是间谍):
s.mockRedisClient.EXPECT().GetStruct(gomock.Any(), cache.BadgesAll, nil).Times(1).DoAndReturn(func(_ context.Context, _ Key, _ any) (*time.Duration, error) {
d := 1 * time.Minute
return &d, nil
})
我已将希望传递给调用的参数指定为“anything”、一个特定的键和一个 nil 值。我指定的 DoAndReturn
回调,特别是根据您的代码,不使用测试代码正在使用的任何参数,因此我忽略了所有这些参数(使用
_
)。不过,正如我在评论中提到的,如果是这种情况,那么 DoAndReturn
与简单地指定 Return
值相比并没有什么特别的好处:d := 1 * time.Minute
s.mockRedisClient.EXPECT().GetStruct(gomock.Any(), cache.BadgesAll, nil).Times(1).Return(&d, nil)
如果您想测试多个调用,并根据所使用的键使用不同的返回值,您可以随时执行以下操作:
values := map[cache.Key]time.Duration{
cache.BadgesAll: 1 *time.Minute,
cache.FooBar: 123 *time.Second,
}
s.mockRedisClient.EXPECT().GetStruct(gomock.Any(), gomock.Any(), gomock.Any()).Times(len(values)).DoAndReturn(func(_ context.Context, k cache.Key, _ any) (*time.Duration, error) {
ret, ok := values[k]
require.True(t, ok) // ensure the key being passed existed in the map
return &ret, nil
})
最后,您可以创建一个
gomock.Matcher
类型,这是一个简单的接口:
type Matcher interface {
// Matches returns whether x is a match.
Matches(x interface{}) bool
// String describes what the matcher matches.
String() string
}
像这样:
type keyMatcher struct {
valid map[cache.Key]struct{} // keys you consider valid
}
func (m keyMatcher) Matches(x any) bool {
v, ok := x.(cache.Key)
if !ok {
return false // input not of type cache.Key
}
_, ok = m.valid[v]
return ok // will return false if key wasn't in the valid map
}
func (m keyMatcher) String() string {
r := "Matches any of the following keys:"
for k := range m.valid {
r = fmt.Sprintf("%s %v", r, k)
}
return r
}
创建此匹配器类型的实例,并用它设置您的模拟:
km := keyMatcher{
valid: map[cache.Key]struct{}{
cache.BagesAll: {},
cache.FooBar: {},
},
}
s.mockRedisClient.EXPECT().GetStruct(gomock.Any(), km, gomock.Any())
然后你就可以开始比赛了。
因此,要对您所说的内容进行另一种解释(
value
始终是
nil
):如果您的意思是在您的DoAndReturn
回调中,参数的值始终是nil
,但您真的不要期望它是这样,那么显然,您尝试测试的代码中有一个错误。如果您不提供该代码,则很难说您在那里缺少什么。我建议您在运行测试时逐步完成测试,看看发生了什么。您正在使用 AssignableToTypeOf
,它只是将其参数的
reflect.TypeOf
类型包装在 AssignableToTypeOfMatcher
参数中。在这种情况下,这意味着指定的调用要么匹配 nil 值,要么匹配指向 models.Badge
的指针。这一切都很好,所以 nil
绝对是有效的。我不确定您期望 value
是什么,如果不是 nil 或有效的指针。如果您没有预料到 nil
值,这会让我们回到您实际测试的代码,要么是错误的,要么是您对其功能的理解不正确。我个人在测试中更倾向于这样做,而不是使用这个AssignableTypeOf
:
s.myMock.EXPECT().Something(gomock.Any()).AnyTimes().DoAndReturn(func(arg any) error {
switch argT := arg.(type) {
case *some.PtrType:
require.NotNil(t, argT)
// other tests, maybe even
argT.Field = "set to some value"
case some.OtherType:
require.Equal(t, argT.Field, "expected value")
default:
return fmt.Errorf("unexpected type passed to Something: %T = %#v", argT, arg)
}
return nil
})
现在我的模拟将接受任意数量的对
Something
方法的调用,并且在我的
DoAndReturn
函数中,我有一个类型开关,它检查参数的类型、值,并且我可以在某些情况下与它们交互与我的测试相关的方式。但是,如果传递的参数是未知类型,我的模拟会返回错误。我还可以在默认情况下添加 t.Fail()
调用来停止测试,因为显然发生了意外的情况。