使用 DoAndReturn 为接口参数赋值

问题描述 投票:0回答:1

我有以下方法:

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
始终为零。有人能指出我在这里缺少什么吗?谢谢

go
1个回答
0
投票

是的,所以有几件事需要考虑

仅回到我正在测试的控制器中,值始终为零。有人能指出我在这里缺少什么吗?

这个语句可以用几种方式来解读:要么

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()
调用来停止测试,因为显然发生了意外的情况。
    

© www.soinside.com 2019 - 2024. All rights reserved.