如何用Gomock测试闭包依赖外部包结构体的Go方法?
问题:Gomock测试闭包依赖model结构体的业务方法
项目代码结构
gitlab.com/myproject/ modules/ itemcategory/ - itemcategorymanager.go - itemcategorymanager_test.go dataacess/ model/ - itemcategorymodel.go
核心代码实现
itemcategorymanager.go
package itemcategory import "gitlab.com/myproject/dataacess/model" type ItemCategoryDM interface{ AddToDB(...) error WithTransaction(toExecute func(dm *model.ItemCategoryModel) error) error } type ItemCategoryManager struct { itemCategoryDM ItemCategoryDM } func (m ItemCategoryManager) AddItemCategoryToDB(...) { if err := m.itemCategoryDM.WithTransaction( func(dm *model.ItemCategoryModel) error { // 业务逻辑:调用model的AddToDB,再执行其他操作 dm.AddToDB(...) return m.DoOtherStuff(...) } ); err != nil { // 错误处理 } } func (m ItemCategoryManager) DoOtherStuff(...) error { // 其他业务逻辑 }
itemcategorymodel.go
package model type ThirdPartyORM interface { WithTransactionDB(func(txn ThirdPartyORM) error) error } type ItemCategoryModel struct { orm ThirdPartyORM } func NewItemCategoryModel(orm ThirdPartyORM) *ItemCategoryModel { return &ItemCategoryModel{orm: orm} } func (m ItemCategoryModel) AddToDB(...) error { // ORM写入逻辑 } func (m ItemCategoryModel) WithTransaction(toExecute func(dm *ItemCategoryModel) error) error { return m.orm.WithTransactionDB(func(txn ThirdPartyORM) error { return toExecute(NewItemCategoryModel(txn)) }) }
遇到的问题
测试itemcategory.AddItemCategoryToDB方法时,WithTransaction的闭包依赖*model.ItemCategoryModel,但model层未定义接口无法直接mock;若将闭包参数改为*ItemCategoryDM会导致循环依赖(model包需导入业务层接口);移接口到其他包不符合项目规范(接口归属使用方),求可行测试方案。
尝试的测试代码
myModel := &model.ItemCategoryModel{} itemCategoryDM := NewMockItemCategoryDM(mockController) itemCategoryDM. EXPECT(). WithTransaction(gomock.Any()). Do(func(toExecute func(dm *model.ItemCategoryModel) error) { err := toExecute(myModel) assert.NoError(t, err) }). Return(nil). Times(1) itemCategoryDM.EXPECT().DoOtherStuff(...).Return(nil).Times(1)
无法mockmyModel.AddToDB(...)的调用行为与返回值。
解决方案:在业务层定义model操作的最小接口
遵循Go接口设计规范(接口定义在使用它的包中),在itemcategory包中定义仅包含业务所需方法的接口,无需修改model包代码,即可实现mock。
步骤1:在itemcategory包中添加最小接口
修改itemcategorymanager.go,新增针对model操作的接口:
package itemcategory import "gitlab.com/myproject/dataacess/model" // 仅定义业务代码中用到的model方法 type ItemCategoryDBActions interface { AddToDB(...) error } type ItemCategoryDM interface{ AddToDB(...) error // 修改闭包参数为接口类型 WithTransaction(toExecute func(dm ItemCategoryDBActions) error) error }
步骤2:调整业务逻辑中的闭包参数
修改AddItemCategoryToDB中的闭包签名,适配新接口:
func (m ItemCategoryManager) AddItemCategoryToDB(...) { if err := m.itemCategoryDM.WithTransaction( // 参数改为ItemCategoryDBActions接口 func(dm ItemCategoryDBActions) error { dm.AddToDB(...) return m.DoOtherStuff(...) } ); err != nil { // 错误处理 } }
model.ItemCategoryModel会自动实现ItemCategoryDBActions接口(因为它有AddToDB方法),无需修改model包代码。
步骤3:编写可mock的测试代码
使用Gomock创建ItemCategoryDBActions的mock实例,在测试中控制AddToDB的行为:
func TestAddItemCategoryToDB(t *testing.T) { mockCtrl := gomock.NewController(t) defer mockCtrl.Finish() // Mock ItemCategoryDBActions接口,控制AddToDB的行为 mockDBActions := NewMockItemCategoryDBActions(mockCtrl) mockDBActions.EXPECT().AddToDB(...).Return(nil).Times(1) // Mock ItemCategoryDM接口 mockItemCategoryDM := NewMockItemCategoryDM(mockCtrl) mockItemCategoryDM.EXPECT().WithTransaction(gomock.Any()). Do(func(toExecute func(dm ItemCategoryDBActions) error) { // 传入mock的DBActions实例执行闭包 err := toExecute(mockDBActions) assert.NoError(t, err) }).Return(nil).Times(1) // Mock DoOtherStuff方法 mockItemCategoryDM.EXPECT().DoOtherStuff(...).Return(nil).Times(1) // 初始化Manager并调用测试方法 manager := ItemCategoryManager{itemCategoryDM: mockItemCategoryDM} manager.AddItemCategoryToDB(...) }
方案优势
- 符合Go接口设计规范:接口定义在业务层(使用方),无需model包导入任何业务层代码,避免循环依赖
- 无需修改model包代码,侵入性极低
- 可精准mock业务所需的model方法,测试逻辑完全可控
内容的提问来源于stack exchange,提问作者Victor Vic
相关产品推荐
相关产品推荐

