Go语言映射对象测试:如何隔离Core层完成API单元测试
如何Mock依赖完成Go语言API包的单元测试
问题背景
我正在尝试为Go语言代码编写单元测试,发现方法Mock的相关内容难度较高。以下是我的代码示例,希望能得到帮助:
API包代码
package api import ( "fmt" "core" ) type createUserDTO struct { Id string } func ApiMethod() { fmt.Println("some incoming api call wit user") incomingUserData := &createUserDTO{Id: "testId"} mapedUser := incomingUserData.mapUser() mapedUser.Create() } func (createUserDTO *createUserDTO) mapUser() core.User { return &core.UserCore{Id: createUserDTO.Id} // 注:原代码存在笔误,core包中实际是CoreUser }
Core包代码
package core import ( "fmt" ) type CoreUser struct{ Id string } type User interface { Create() } func (user CoreUser) Create() { fmt.Println("Do Stuff") }
我的问题是:如何在不测试core包(尤其是其中的Create()方法)的前提下,完成api包中所有公共方法的单元测试?
解决方案
核心问题是当前API包的代码硬编码依赖了core包的具体实现,mapUser方法直接返回core.CoreUser实例,导致无法替换这个依赖来MockCreate方法。我们需要通过依赖注入的方式,让User实例的创建逻辑可被替换。
方法一:通过全局工厂函数实现依赖替换
改造API包代码,引入全局工厂函数来创建User实例,这样测试时可以替换该函数:
package api import ( "fmt" "core" ) type createUserDTO struct { Id string } // UserFactory 定义创建User实例的工厂类型 type UserFactory func(id string) core.User // 默认工厂函数,返回真实的core.CoreUser var userFactory UserFactory = func(id string) core.User { return &core.CoreUser{Id: id} } func ApiMethod() { fmt.Println("some incoming api call wit user") incomingUserData := &createUserDTO{Id: "testId"} mapedUser := incomingUserData.mapUser() mapedUser.Create() } func (dto *createUserDTO) mapUser() core.User { return userFactory(dto.Id) }
编写单元测试,定义Mock的User实现并替换工厂函数:
package api import ( "testing" "core" ) // MockUser 实现core.User接口的Mock结构体,用于跟踪Create方法调用 type MockUser struct { CreateCalled bool } func (m *MockUser) Create() { m.CreateCalled = true } func TestApiMethod(t *testing.T) { // 保存原工厂函数,测试结束后恢复 originalFactory := userFactory defer func() { userFactory = originalFactory }() mockUser := &MockUser{} // 替换工厂函数,返回Mock实例并验证参数 userFactory = func(id string) core.User { if id != "testId" { t.Errorf("期望ID为'testId',实际得到'%s'", id) } return mockUser } // 调用待测试的ApiMethod ApiMethod() // 验证Create方法是否被调用 if !mockUser.CreateCalled { t.Error("User的Create方法未被调用") } }
方法二:通过结构体封装实现依赖注入(推荐)
这种方式更符合Go的最佳实践,避免全局变量,通过结构体封装依赖:
package api import ( "fmt" "core" ) type API struct { userFactory UserFactory } type UserFactory func(id string) core.User // NewAPI 创建API实例,允许传入自定义工厂函数,默认使用真实实现 func NewAPI(factory UserFactory) *API { if factory == nil { factory = func(id string) core.User { return &core.CoreUser{Id: id} } } return &API{userFactory: factory} } type createUserDTO struct { Id string } func (a *API) ApiMethod() { fmt.Println("some incoming api call wit user") incomingUserData := &createUserDTO{Id: "testId"} mapedUser := incomingUserData.mapUser(a.userFactory) mapedUser.Create() } func (dto *createUserDTO) mapUser(factory UserFactory) core.User { return factory(dto.Id) }
对应的单元测试代码:
package api import ( "testing" "core" ) type MockUser struct { CreateCalled bool } func (m *MockUser) Create() { m.CreateCalled = true } func TestApiMethod(t *testing.T) { mockUser := &MockUser{} // 创建API实例时传入Mock工厂函数 api := NewAPI(func(id string) core.User { if id != "testId" { t.Errorf("期望ID为'testId',实际得到'%s'", id) } return mockUser }) api.ApiMethod() if !mockUser.CreateCalled { t.Error("User的Create方法未被调用") } }
总结
两种方法的核心都是解除API包对core包具体实现的硬依赖,通过可替换的工厂函数或依赖注入,让测试时能使用Mock的User实例,从而只验证API包的逻辑:
- 验证
mapUser是否正确传递了ID参数 - 验证
Create方法是否被正确调用
内容的提问来源于stack exchange,提问作者TheBohne
相关产品推荐
相关产品推荐

