gomock简明教程
1.定义
gomock是一个用于Go语言的Mock框架,
它可以帮助开发者在单元测试中模拟(Mock)接口的行为,
从而隔离被测试代码与外部依赖,使测试更加独立、稳定和高效。
项目原地址为 golang/mock
2023年6月之后,代码库已经归档,官方推荐使用go.uber.org/mock
2.安装
go install go.uber.org/mock/mockgen@latest
3.生成Mock代码
有文件itf.go
mockgen -source=./itf.go -destination=./mock.go -package=mymock
- -source:指定包含接口定义的源文件。
- -destination:用于写入结果源代码的文件。
- -package:指定生成的Mock代码所在的包名。
4.单元测试
预设的请求,可以按照顺序调用或非顺序调用
4.1 非顺序调用
import (
"fmt"
"go.uber.org/mock/gomock"
"testing"
)
func TestFoo(t *testing.T) {
// 创建Mock控制器
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := NewMockFoo(ctrl)
m.EXPECT().Bar(1).Return(1)
m.EXPECT().Bar(2).Return(2)
fmt.Println("--1--", m.Bar(2))
fmt.Println("--2--", m.Bar(1))
}
output:
--1-- 2
--2-- 1
可以按照任意顺序调用Bar()
4.2 顺序调用
func TestFoo2(t *testing.T) {
// 创建Mock控制器
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := NewMockFoo(ctrl)
gomock.InOrder( // 必须按照指定顺序调用Bar()
m.EXPECT().Bar(1).Return(1),
m.EXPECT().Bar(2).Return(2),
)
fmt.Println("--1--", m.Bar(2))
fmt.Println("--2--", m.Bar(1))
}
单元测试运行错误
=== RUN TestFoo2
mymock_test.go:33: Unexpected call to *mymock.MockFoo.Bar([2]) at mymock/mymock_test.go:33 because:
expected call at mymock/mymock_test.go:29 doesn't match the argument at index 0.
Got: 2 (int)
Want: is equal to 1 (int)
expected call at mymock/mymock_test.go:30 doesn't have a prerequisite call satisfied:
*mymock.MockFoo.Bar(is equal to 1 (int)) mymock/mymock_test.go:29
should be called before:
*mymock.MockFoo.Bar(is equal to 2 (int)) mymock/mymock_test.go:30
controller.go:251: missing call(s) to *mymock.MockFoo.Bar(is equal to 1 (int)) mymock/mymock_test.go:29
controller.go:251: missing call(s) to *mymock.MockFoo.Bar(is equal to 2 (int)) mymock/mymock_test.go:30
controller.go:251: aborting test due to missing call(s)
--- FAIL: TestFoo2 (0.00s)
4.3 按照指定函数输出返回值
func TestFoo3(t *testing.T) {
// 创建Mock控制器
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := NewMockFoo(ctrl)
m.EXPECT().Bar(1).DoAndReturn(func(x int) int {
return x + 10
})
m.EXPECT().Bar(2).DoAndReturn(func(x int) int {
return x + 10
})
fmt.Println("--1--", m.Bar(1))
fmt.Println("--2--", m.Bar(2))
}
output:
--1-- 11
--2-- 12
4.4 命中call,触发hook操作
func TestFoo4(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := NewMockFoo(ctrl)
// Do中传入的函数相当于Hook函数
m.EXPECT().Bar(1).Do(func(x int) int {
fmt.Println("x: ", x)
return -1 // 返回值无意义
}).Return(10)
fmt.Println("--1--", m.Bar(1))
}
output:
x: 1
--1-- 10
4.5 其它常用函数
func TestFoo5(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := NewMockFoo(ctrl)
m.EXPECT().Bar(1).Return(1)
// gomock.Any() 表示匹配任意参数
// AnyTimes() 表示匹配任意多次
m.EXPECT().Bar(gomock.Any()).Return(100).AnyTimes()
fmt.Println("--1--", m.Bar(1))
fmt.Println("--2--", m.Bar(2))
fmt.Println("--3--", m.Bar(3))
fmt.Println("--4--", m.Bar(4))
}
output:
--1-- 1
--2-- 100
--3-- 100
--4-- 100
4.6 使用自定义的匹配器
gomock默认使用eqMatcher来判断请求参数列表
和预设的参数列表
是否相同
eqMatcher.Matches() -> reflect.DeepEqual()
type eqMatcher struct {
x any
}
func (e eqMatcher) Matches(x any) bool {
// In case, some value is nil
if e.x == nil || x == nil {
return reflect.DeepEqual(e.x, x)
}
// Check if types assignable and convert them to common type
x1Val := reflect.ValueOf(e.x)
x2Val := reflect.ValueOf(x)
if x1Val.Type().AssignableTo(x2Val.Type()) {
x1ValConverted := x1Val.Convert(x2Val.Type())
return reflect.DeepEqual(x1ValConverted.Interface(), x2Val.Interface())
}
return false
}
我们可以自定义自己的Matcher, 满足Matcher接口即可
type Matcher interface {
// Matches returns whether x is a match.
Matches(x any) bool
// String describes what the matcher matches.
String() string
}
示例
type CustomMatcher struct {
Value *Car
}
func NewCustomMatcher(value *Car) *CustomMatcher {
return &CustomMatcher{Value: value}
}
func (m *CustomMatcher) Matches(x interface{}) bool {
value2 := x.(*Car)
fmt.Println("value1:", m.Value, "value2:", value2)
return m.Value.Age == value2.Age
}
func (m *CustomMatcher) String() string {
return fmt.Sprintf("CustomMatcher(%v)", m.Value)
}
func TestDealer(t *testing.T) {
ctrl := gomock.NewController(t)
defer ctrl.Finish()
m := NewMockDealer(ctrl)
m.EXPECT().Evaluate(NewCustomMatcher(&Car{Color: "red", Age: 1})).Return(100).AnyTimes()
fmt.Println("--1--", m.Evaluate(&Car{Color: "red", Age: 1}))
fmt.Println("--2--", m.Evaluate(&Car{Color: "blue", Age: 1})) // 都会命中预设的Call
}
output:
value1: &{1 red} value2: &{1 red}
--1-- 100
value1: &{1 red} value2: &{1 blue}
--2-- 100
完整代码见
mygomock
5.总结
gomock的功能非常强大,配合gomonkey使用,足以应对日常的单元测试场景。