目录
使用gomock包
1. 安装mockgen
go get -u github.com/golang/mock/gomock
go get -u github.com/golang/mock/mockgen
2. 定义接口
比如我们想要 mock 一个 infra 包中的名为 GetFromRedis 的函数:
// pkg/infra/infra.go
package infra
func GetFromRedis(cli *redis.Client, key string) (string, error) {
// 为什么这里要打印一下呢,因为一行会触发内联优化,影响单测结果...
fmt.Println("cannot reach here if is ut")
return cli.Get(context.Background(), key).Result()
}
那需要定义一个 interface 将此GetFromRedis 的函数封装起来:
type Redis interface {
GetFromRedis(cli *redis.Client, key string) (string, error)
}
这个 interface 必须定义,否则 gomock 无法生成 mock 方法。
3. 生成mock文件
在命令行执行:
mockgen -source=pkg/infra/infra.go -destination=pkg/infra/mock/mock_infra.go -package=mock
其中 source 为待 mock 的文件,destination 为 mock 文件生成的位置。
mockgen 工具将为你生成 pkg/infra/mock/mock_infra.go 文件。
4. 在单测中使用mock的函数
mock 就可以这样写:
// pkg/infra/infra_test.go
func Test_gomock(t *testing.T) {
// 创建gomock控制器
ctrl := gomock.NewController(t)
defer ctrl.Finish()
var redisCli *redis.Client
// 创建模拟对象
m := mock.NewMockRedis(ctrl)
// 定义预期行为
m.EXPECT().
GetFromRedis(redisCli, "test_key"). // 模拟入参
Return("test value", nil) // 模拟返回值
// 调用被测方法,获取实际结果
resVal, err := m.GetFromRedis(redisCli, "test_key")
assert.NoError(t, err)
assert.Equal(t, "test value", resVal)
}
5. gomock 包的使用问题
- 用起来很麻烦,要定义 interface,还要执行 mockgen 命令
- 无法 mock 私有成员的函数
使用gomonkey包
当你用了 gomonkey 之后!就再也不会想用 gomock 了!(gomonkey打钱)
1. mock 一个包函数
还是上述例子,比如我们想要 mock 一个 infra 包中的名为 GetFromRedis 的函数:
// pkg/infra/infra.go
package infra
func GetFromRedis(cli *redis.Client, key string) (string, error) {
fmt.Println("cannot reach here if is ut")
return cli.Get(context.Background(), key).Result()
}
无需前置操作,直接在单测中写 mock 代码:
// pkg/infra/infra_test.go
func Test_gomonkey_function(t *testing.T) {
// 创建gomonkey对象, 模拟GetFromRedis预期行为
patch := gomonkey.ApplyFunc(GetFromRedis, func(_ *redis.Client, _ string) (string, error) {
return "test value", nil
})
defer patch.Reset()
// 调用被测方法,获取实际结果
resVal, err := GetFromRedis(&redis.Client{}, "test_key")
assert.NoError(t, err)
assert.Equal(t, "test value", resVal)
}
ApplyFunc 的第一个参数是被测函数,第二个参数是预期的行为函数(注意入参出参定义要与被测函数完全一致)
当然你还可以用 patch 去 mock 更多函数:
patch.ApplyFunc(...)
2. mock 一个公有成员函数
比如我们有如下公有成员函数 PublicRedisHandler.GetFromRedis:
// pkg/infra/infra.go
package infra
type PublicRedisHandler struct {
cli *redis.Client
}
func NewPublicRedisHandler(cli *redis.Client) *PublicRedisHandler {
return &PublicRedisHandler{
cli: cli,
}
}
func (r *PublicRedisHandler) GetFromRedis(key string) (string, error) {
fmt.Println("cannot reach here if is ut")
return r.cli.Get(context.Background(), key).Result()
}
在单测中写法为:
// pkg/infra/infra_test.go
func Test_gomonkey_public_member(t *testing.T) {
// mock 公共成员方法
patch := gomonkey.ApplyMethod(reflect.TypeOf(&PublicRedisHandler{}), "GetFromRedis", func(_ *PublicRedisHandler, key string) (string, error) {
return "test value", nil
})
defer patch.Reset()
// 调用被测方法,获取实际结果
r := NewPublicRedisHandler(&redis.Client{})
resVal, err := r.GetFromRedis("test_key")
assert.NoError(t, err)
assert.Equal(t, "test value", resVal)
}
其中 ApplyMethod 的第一个参数是成员的类型,第二个参数是函数名称,第三个参数是预期的行为函数(注意入参的第一个参数必须是成员对象)
3. mock 一个私有成员函数
那如果我们想要 mock 的函数是一个私有成员下的函数呢:
// pkg/infra/infra.go
package infra
type privateRedisHandler struct {
cli *redis.Client
}
func newewPrivateRedisHandler(cli *redis.Client) *privateRedisHandler {
return &privateRedisHandler{
cli: cli,
}
}
func (r *privateRedisHandler) GetFromRedis(key string) (string, error) {
// 为什么这里要打印一下呢,因为一行会触发内联优化,影响单测结果...
fmt.Println("cannot reach here if is ut")
return r.cli.Get(context.Background(), key).Result()
}
那么写法为:
// pkg/infra/infra_test.go
func Test_gomonkey_private_member(t *testing.T) {
// mock 私有成员方法
patch := gomonkey.ApplyPrivateMethod(reflect.TypeOf(&privateRedisHandler{}), "GetFromRedis", func(_ *privateRedisHandler, key string) (string, error) {
return "test value", nil
})
defer patch.Reset()
// 调用被测方法,获取实际结果
r := newPrivateRedisHandler(&redis.Client{})
resVal, err := r.GetFromRedis("test_key")
assert.NoError(t, err)
assert.Equal(t, "test value", resVal)
}
其中 ApplyPrivateMethod 的第一个参数是成员的类型,第二个参数是函数名称,第三个参数是预期的行为函数(注意入参的第一个参数必须是成员对象)