什么是 go 模拟
go mock 是一个模拟框架,允许开发人员为我们的接口创建模拟结构并定义其行为。
例如,它可以帮助我们生成 reponsitory 的模拟实现,并根据某些输入定义预期输出。
原始仓库:https://github.com/golang/mock
现在不再维护了,所以我们应该使用 https://github.com/uber-go/mock 来代替。
安装
go install go.uber.org/mock/mockgen@latest
主要特征
- mock generation:gomock 包含一个名为mockgen 的工具,可以自动生成接口的模拟实现。
-
灵活的期望:使用 gomock,您可以对模拟对象的行为定义精确的期望,例如:
- 它应该接收的参数
- 以及它应该返回的值
- 应调用方法的具体次数,最小或最大次数
mockgen命令
按照此文档了解如何使用mockgen cli:
https://github.com/uber-go/mock?tab=readme-ov-file#running-mockgen
用法
假设你有一个 iuserrepo 接口,其中包含一些方法:
ports.go
package user
type iuserrepo interface {
getuserbyid(id int) (*user, error)
insert(user user) error
update(id int, user user) error
}
domain.go
package user
type user struct {
id int
name string
}
服务.go
package user
import "fmt"
type userservice struct {
repo iuserrepo
}
var invaliduseriderror = fmt.errorf("invalid user id")
func (u *userservice) upsert(user user) error {
if user.id
<p><strong>1.运行mockgen来生成mock实例</strong><br></p>
<pre class="brush:php;toolbar:false">go run go.uber.org/mock/mockgen@latest -source=interface.go -destination=mock.go -package=user
- source:指定包含repository接口的文件。
- 目的地:指定将写入生成的模拟代码的文件。
- package:指定生成的mock的包名。
2.指定期望
service_test.go
package user
import (
"go.uber.org/mock/gomock"
"github.com/stretchr/testify/assert"
"testing"
)
func TestUpsertUser(t *testing.T) {
mockCtl := gomock.NewController(t)
defer mockCtl.Finish()
tests := []struct {
name string
user User
specifyFunctionCalls func(mock *MockIUserRepo)
expectedError error
}{
{
user: User{ID: 1, Name: "User 1"},
name: "Should insert",
specifyFunctionCalls: func(mockRepo *MockIUserRepo) {
mockRepo.EXPECT().GetUserByID(1).Return(nil, nil).Times(1)
mockRepo.EXPECT().Insert(User{ID: 1, Name: "User 1"}).Return(nil).Times(1)
},
},
{
name: "User existed - Should update",
user: User{ID: 1, Name: "New User Name"},
specifyFunctionCalls: func(mockRepo *MockIUserRepo) {
mockRepo.EXPECT().GetUserByID(1).Return(&User{ID: 1, Name: "User 1"}, nil).Times(1)
mockRepo.EXPECT().Update(1, User{ID: 1, Name: "New User Name"}).Return(nil).Times(1)
},
},
{
expectedError: invalidUserIDError,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mockRepo := NewMockIUserRepo(mockCtl)
if test.specifyFunctionCalls != nil {
test.specifyFunctionCalls(mockRepo)
}
userService := UserService{repo: mockRepo}
err := userService.Upsert(test.user)
assert.Equal(t, test.expectedError, err)
})
}
}
在上面的测试文件中:
- specifyfunctioncalls 允许我们自定义模拟函数期望,必须调用哪些函数以及必须调用这些函数多少次。
- 如果您尝试在specifyfunctioncalls规范中添加冗余函数(例如在第一个测试中添加mockrepo.expect().update(....)),您的测试将因错误而失败:missing call(s).
- 如果您的服务调用尚未指定的函数,您的测试将因错误而失败:该接收者没有预期的“插入”方法调用。
源代码
https://github.com/huantt/gomock-demo