mock 是Go官方提供的测试框架,很好的集成testing包,实现对interface的mock。

安装mock

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
> go version
go version go1.16.6 darwin/amd64 

> go install github.com/golang/mock/mockgen@v1.6.0
go: downloading github.com/golang/mock v1.6.0
go: downloading golang.org/x/mod v0.4.2
go: downloading golang.org/x/tools v0.1.1
go: downloading golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1
go: downloading golang.org/x/sys v0.0.0-20210510120138-977fb7262007

> mockgen -version
v1.6.0

> mockgen -help

mockgen支持选项:

  • -source:包含要mock的接口的文件。
  • -destination:要将生成的源代码写入其中的文件。如果未设置此值,则代码将打印到标准输出。
  • -package:用于生成的模拟类源代码的包。如果不设置此项,则包名称mock_与输入文件的包连接。
  • -import:应在生成的源代码中使用的显式导入列表,指定为 foo=bar/baz 形式的逗号分隔元素列表,其中 bar/baz 是要导入的包,foo 是生成的源代码中用于包的标识符。
  • -aux_files: 应查阅的其他文件列表,以解决例如在不同文件中定义的嵌入式接口。这被指定为以逗号分隔的元素列表 foo=bar/baz.go,其中 bar/baz.go 是源文件,foo 是 -source 文件使用的该文件的包名
  • -build_flags:(仅反射模式)标志逐字传递以进行构建

mockgen使用示例

mockgen有两种操作模式:源文件和反射。

源文件模式使用-source指定包含interface定义的文件,生成mock类文件。

1
mockgen -source=foo.go [other options]

反射模式没有标识参数,第一个参数是项目导入路径,第二个参数是interface名称(多个用逗号分隔)

1
mockgen database/sql/driver Conn,Driver

准备接口文件foo.go,接口内容如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
package gomock

type Foo interface {
	Bar(x int) int
}

func SUT(f Foo) int {
	return f.Bar(99)
}

使用mockgen生成mock文件,执行如下:

1
mockgen -source=./foo.go -destination=./mock_foo.go -package=gomock

生成的mock文件内容:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
// Code generated by MockGen. DO NOT EDIT.
// Source: ./foo.go

// Package gomock is a generated GoMock package.
package gomock

import (
	reflect "reflect"

	gomock "github.com/golang/mock/gomock"
)

// MockFoo is a mock of Foo interface.
type MockFoo struct {
	ctrl     *gomock.Controller
	recorder *MockFooMockRecorder
}

// MockFooMockRecorder is the mock recorder for MockFoo.
type MockFooMockRecorder struct {
	mock *MockFoo
}

// NewMockFoo creates a new mock instance.
func NewMockFoo(ctrl *gomock.Controller) *MockFoo {
	mock := &MockFoo{ctrl: ctrl}
	mock.recorder = &MockFooMockRecorder{mock}
	return mock
}

// EXPECT returns an object that allows the caller to indicate expected use.
func (m *MockFoo) EXPECT() *MockFooMockRecorder {
	return m.recorder
}

// Bar mocks base method.
func (m *MockFoo) Bar(x int) int {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "Bar", x)
	ret0, _ := ret[0].(int)
	return ret0
}

// Bar indicates an expected call of Bar.
func (mr *MockFooMockRecorder) Bar(x interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Bar", reflect.TypeOf((*MockFoo)(nil).Bar), x)
}

对生成的mock文件无需修改,生成后编写接口的单元测试方法:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package gomock

func TestFoo(t *testing.T) {
	ctrl := gomock.NewController(t)

	// Assert that Bar() is invoked.
	defer ctrl.Finish()

	m := NewMockFoo(ctrl)

	// Asserts that the first and only call to Bar() is passed 99.
	// Anything else will fail.
	m.
		EXPECT().
		Bar(gomock.Eq(99)).
		Return(101)

	SUT(m)
}

  • gomock.NewController:返回 gomock.Controller,它代表 mock 生态系统中的顶级控件。定义了 mock 对象的范围、生命周期和期待值。另外它在多个 goroutine 中是安全的
  • NewMockFoo:创建一个新的 mock 实例
  • m.EXPECT().Bar(gomock.Eq(99)).Return(101):这里有三个步骤,EXPECT()返回一个允许调用者设置期望和返回值的对象。Bar(gomock.Eq(99)) 是设置入参并调用 mock 实例中的方法。Return(101) 是设置先前调用的方法出参。简单来说,就是设置入参并调用,最后设置返回值

运行测试

1
2
3
4
5
6
> go test -mod=mod -v -run="TestFoo"
=== RUN   TestFoo
--- PASS: TestFoo (0.00s)
PASS
ok      tools/gomock    0.007s

参考