外观
mocker
1.appConfig
mock := mocker.Create()
mock.Struct(&common.InstanceCommon{}).Method("GetConfig").Return(&config.ServerConfig{
App: config.AppConfig{
ZhiLaiAppId: "12222",
ZhiLaiAppSecret: "1222222",
},
})2.redis
// 在当前包定义个同样struct,需要包含相同的属性,struct的方法不用写
type cmdable struct {
process func(cmd redis.Cmder) error
}
// 方式1
mock.Pkg("github.com/go-redis/redis").ExportStruct("*cmdable").Method("Get").Apply(func(_ *cmdable, s string) *redis.StringCmd {
if s == "attendance_session_Hello" {
return redis.NewStringResult("testMock", nil)
} else {
return redis.NewStringResult("mockelse", nil)
}
})
// 方式2
mock.Pkg("github.com/go-redis/redis").ExportStruct("*cmdable").Method("Get").As(func(*cmdable, string) *redis.StringCmd{
return nil
}).When(&cmdable{}, "attendance_session_Hello").Return(redis.NewStringResult("mockTest", nil))3.指针方法mock receiver
// 被mock方法:func (u *ZhiLaiUser) GetUser(orgId string) error {}
mock := mocker.Create()
mock.Struct(&model.ZhiLaiUser{}).Method("GetUser").When("12345").
Apply(func(u *model.ZhiLaiUser, orgId string) error {
u.Uid = uid
u.OrgId = "12345"
u.OrgName = "测试"
u.ProvinceCode = "1"
u.PeopleNum = 2000
return nil
})4.指针参数
// 被mock方法:func DoRequest(logger *zap.SugaredLogger, req *Request, rsp interface{}) error
mock := mocker.Create()
mock.Func(httputil.DoRequest).Apply(func(_ *zap.SugaredLogger, req *httputil.Request, rsp interface{}) error {
checkRsp, ok := rsp.(*zlCheckRsp)
if ok {
checkRsp.Code = 0
checkRsp.Data = []zlCheckData{{
NormType: "1-1",
Msg: "很遗憾",
MeanValue: "22.83",
}}
rsp = checkRsp
}
return nil
})5.grpc
mock := mocker.Create()
// mock gprc的接口方法
clientServiceClient := (clientManager.ClientServiceClient)(nil)
mock.Interface(&clientServiceClient).Method("GetQWGJInstances").As(func(ictx *mocker.IContext,
ctx context.Context, in *clientManager.GetQWGJInstanceReq,
opts ...grpc.CallOption) (*clientManager.GetQWGJInstanceRsp, error) {
return nil, nil
}).Return(&clientManager.GetQWGJInstanceRsp{
Instances: []*clientManager.Instance{
{
InstanceId: "123",
QWGJName: "测试",
IsBond: true,
IsAuth: false,
CreatedTime: "2021-09-18 22:44:33",
EmployeeNum: 20,
AdminActive: 10,
EmployeeActive: 30,
},
{
InstanceId: "124",
QWGJName: "测试4",
IsBond: false,
IsAuth: true,
CreatedTime: "2021-06-18 22:44:33",
EmployeeNum: 30,
AdminActive: 9,
EmployeeActive: 3,
},
},
}, nil)
// 设置Common的gprc客户端
clients := &common.Clients{
ClientManagerServiceClient: clientServiceClient,
}
logger, _ := zap.NewProduction()
com, _ := common.NewCommonWithClientsForTest(context.Background(), logger.Sugar(), clients)
ds := &DealService{
com,
}目前公司众多项目使用外界开源的gomonkey框架进行函数的mock,因为gomoneky存在着多种问题,所以在新的项目禁止使用gomonkey,禁止理由见单元测试禁用gomonkey的理由。新项目统一推荐使用goom/mocker,goom/mocker具有如下特性:
- 私有(未导出)函数(或方法)的mock, 普通函数的mock
- mock过程中回调原函数(线程安全, 支持并发单测),已升级为trampoline模式
- 异常注入,对函数调用支持异常注入,延迟模拟等稳定性测试
- 支持interface mock
- 所有操作都是并发安全的
只需要在使用mock的测试文件中引入如下依赖,就可以开始愉快的使用goom/mocker了
import "git.code.oa.com/goom/mocker"注意:按照go编译规则,短函数会被内联优化,导致无法mock的情况,编译参数需要加上 -gcflags=all=-l 关闭内联,如:
go test -gcflags=all=-l hello.go下面将会使用几个例子结合日常编写单测过程中需要使用到mock的常见场景来详细介绍goom/mocker的用法
6. 基本使用
1.1 函数Mock
// 函数定义如下
func foo(i int) int {
return i
}
// mock示例
// 创建当前包的mocker
mock := mocker.Create()
// mock函数foo并设定返回值为1
mock.Func(foo).Return(1)
// mock函数foo,使用Apply方法设置回调函数
mock.Func(foo).Apply(func(int) int {
return 1
})1.2 结构体方法Mock
// 结构体定义如下
type fake struct{}
func (f *fake) Call(i int) int {
return i
}
// 私有方法
func (f *fake) call(i int) int {
return i
}
// mock示例
// 创建当前包的mocker
mock := mocker.Create()
// mock 结构体fake的方法Call并设置其回调函数
mock.Struct(&fake{}).Method("Call").Apply(func(_ *fake, i int) int {
return i * 2
})
// mock 结构体fake的方法Call并返回1
mock.Struct(&fake{}).Method("Call").Return(1)
// mock 结构体fake的私有方法call, mock前先调用ExportMethod将其导出,并设置其回调函数
mock.Struct(&fake{}).ExportMethod("call").Apply(func(_ *fake, i int) int {
return i * 2
})
// mock 结构体fake的私有方法call, mock前先调用ExportMethod将其导出为函数类型,后续支持设置When, Return等
// As调用之后,请使用Return或When API的方式来指定mock返回。
mock.Struct(&fake{}).ExportMethod("call").As(func(_ *fake, i int) int {
return i * 2
}).Return(1)1.3 接口Mock
接口定义举例:
// I 定义接口
type I interface {
Call(int) int
Call1(string) string
call2(int32) int32
}接口变量mock
mock := mocker.Create()
// 接口变量
i := (I)(nil)
// 将Mock应用到接口变量(仅对该变量有效)
mock.Interface(&i).Method("Call").Apply(func(ctx *mocker.IContext, i int) int {
return 3
})
mock.Interface(&i).Method("Call1").Apply(func(ctx *mocker.IContext, i string) string {
return "ok"
})
s.Equal(3, i.Call(1), "interface mock check")
s.Equal("ok", i.Call1(""), "interface mock check")
// Mock重置, 接口变量将恢复原来的值
mock.Reset()
s.Equal(nil, i, "interface mock reset check")2 高阶用法
上面是goom/mocker的基本用法,此外,我们还提供了一些更有高阶的功能,比如对其他package的私有方法Mock
2.1 其它包函数Mock
// 针对其它包的mock示例
// 创建指定包的mocker,设置引用路径
mock := mocker.Create()
// mock函数foo1并设置其代理函数
mock.Pkg("git.code.oa.com/goom/mocker_test").ExportFunc("foo1").Apply(func(i int) int {
return i * 3
})
// mock函数foo1并设置其返回值
mock.ExportFunc("foo1").As(func(i int) int {
return 0
}).Return(1)2.2 其他包结构体方法Mock
// 针对其它包的mock示例
// 创建指定包的mocker,设置引用路径
mock := mocker.Create()
// mock其它包的私有结构体fake的私有方法call,并设置其代理函数
// 如果参数是私有的,那么需要在当前包fake一个同等结构的struct,fake结构体要和原私有结构体的内存结构对齐
// 注意: 如果方法是指针方法,那么需要给struct加上*,比如:ExportStruct("*fake")
mock.Pkg("git.code.oa.com/goom/mocker_test").ExportStruct("fake").Method("call").Apply(func(_ *fake, i int) int {
return i * 2
})
// mock其它包的私有结构体fake的私有方法call,并设置其返回值
mock.ExportStruct("fake").Method("call").As(func(_ *fake, i int) int {
return i * 2
}).Return(1)2.3 根据参数定义多次返回
mock := mocker.Create()
// 设置函数foo当传入参数为1时,第一次返回3,第二次返回2
mock.Func(foo).When(1).Return(3).AndReturn(2)2.4 在代理函数中使用原函数
mock := mocker.Create()
// 定义原函数,用于占位,实际不会执行该函数体
var origin = func(i int) int {
// 函数体长度必须大于一定值, 所以随意加一些代码进行填充
fmt.Println("origin func placeholder")
return 0 + i
}
mock.Func(foo1).Origin(&origin).Apply(func(i int) int {
// 调用原函数
originResult := origin(i)
// 加入延时逻辑等
time.Sleep(time.Seconds)
return originResult + 100
})