go单元测试 mock 构造仿对象

使用testify库中的mock实现

准备一个接口
db.go

package mock

import "fmt"

type DB interface {
	Get(key string) (int, error)
	Add(key string, value int) error
}

func GetFromDB(db DB, key string) int {
	val, err := db.Get(key)
	if err != nil {
		fmt.Println(err)
		return -1
	}
	return val
}

db_test.go

package mock

import (
	"github.com/golang/mock/gomock"
	"github.com/stretchr/testify/assert"
	"github.com/stretchr/testify/mock"
	"testing"
)

// mock对象
// 需要组合mock.Mock
type TestifyMockDB struct {
	mock.Mock
}

// 实现DB接口,重写方法
func (t *TestifyMockDB) Get(key string) (int, error) {
	// 调用mock.Mock的Call方法,传入参数
	// arguments保存返回值
	arguments := t.Called(key)
	return arguments.Int(0), arguments.Error(1) // 按序获取对应类型的返回值
}

func (t *TestifyMockDB) Add(key string, value int) error {
	arguments := t.Called(key, value)
	return arguments.Error(0)
}

func TestGetFromDB(t *testing.T) {
	mockDB := TestifyMockDB{}
	// 打桩
	// 调用Get方法传入yimin时,返回1,nil
	mockDB.On("Get", "yimin").Return(1, nil)
	val, err := mockDB.Get("yimin")
	assert.NoError(t, err)
	assert.Equal(t, 1, val)
}

PS D:\dev\project\common-go\mock> go test -v -run=TestGetFromDB
=== RUN   TestGetFromDB2
--- PASS: TestGetFromDB2 (0.00s)
PASS
ok      common-go/mock  0.054s

这种方式需要我们自己定义Mock对象,并实现接口
第二种方式使用go官方的库gomock实现,通过命令实现自动生成Mock对象,相对手动实现较为简单

使用gomock库实现

安装gomock

go install github.com/golang/mock/mockgen@v1.6.0

执行命名生成文件

mockgen -source .\db.go -destination db_mock.go

以上命令会在db.go同级目录生成db_mock.go文件

大概就是给我们自动生成了Mock对象,并且实现了DB接口

文件内容不用改动

// Code generated by MockGen. DO NOT EDIT.
// Source: .\db.go
// mockgen -source .\db.go -destination db_mock.go

package mock

import (
	reflect "reflect"

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

// MockDB is a mock of DB interface.
type MockDB struct {
	ctrl     *gomock.Controller
	recorder *MockDBMockRecorder
}

// MockDBMockRecorder is the mock recorder for MockDB.
type MockDBMockRecorder struct {
	mock *MockDB
}

// NewMockDB creates a new mock instance.
func NewMockDB(ctrl *gomock.Controller) *MockDB {
	mock := &MockDB{ctrl: ctrl}
	mock.recorder = &MockDBMockRecorder{mock}
	return mock
}

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

// Add mocks base method.
func (m *MockDB) Add(key string, value int) error {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "Add", key, value)
	ret0, _ := ret[0].(error)
	return ret0
}

// Add indicates an expected call of Add.
func (mr *MockDBMockRecorder) Add(key, value interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Add", reflect.TypeOf((*MockDB)(nil).Add), key, value)
}

// Get mocks base method.
func (m *MockDB) Get(key string) (int, error) {
	m.ctrl.T.Helper()
	ret := m.ctrl.Call(m, "Get", key)
	ret0, _ := ret[0].(int)
	ret1, _ := ret[1].(error)
	return ret0, ret1
}

// Get indicates an expected call of Get.
func (mr *MockDBMockRecorder) Get(key interface{}) *gomock.Call {
	mr.mock.ctrl.T.Helper()
	return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Get", reflect.TypeOf((*MockDB)(nil).Get), key)
}

接下来还是编写测试方法

func TestGetFromDB2(t *testing.T) {
	ctl := gomock.NewController(t)
	mockDB := NewMockDB(ctl) // 这是自动生成的db_mock.go中的方法
	// 打桩
	mockDB.EXPECT().Get("yimin").Return(1, nil).Times(1)
	res := GetFromDB(mockDB, "yimin")
	assert.Equal(t, 1, res)

	// 返回前执行一段函数
	mockDB.EXPECT().Get("yimin2").DoAndReturn(func(key string) (int, error) {
		t.Logf("Get called with %s", key)
		return 2, nil
	})
	yimin2Val, err := mockDB.Get("yimin2")
	assert.NoError(t, err)
	assert.Equal(t, 3, yimin2Val)

	// 任意参数
	mockDB.EXPECT().Add(gomock.Any(), gomock.Any()).Return(nil).Times(1)
	err = mockDB.Add("yimin", 1)
	assert.NoError(t, err) // 不返回任何错误
	t.Logf("Add called with yimin and 1")

	// 需要注意的是,通过EXPECT构造的用例必须都要调用,否则会报错
	// 以下代码加不加都一样
	ctl.Finish() // 断言mockDB的EXPECT()方法是否都已经被调用
}

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值