Golang单元测试mock MongoDB接口时localhost报ERR_EMPTY_RESPONSE
问题根因
报错ERR_EMPTY_RESPONSE本质是服务触发panic崩溃,没有返回任何HTTP响应,和数据库连接本身无关,触发逻辑如下:
- 你将原代码中持有真实mgo集合实例的全局变量
userRoleDB,替换为接口类型的userRoleMockDB后,没有在服务启动的数据库初始化流程中给这个接口变量赋值真实的集合实例。Go中接口类型全局变量的默认零值为nil,业务调用LoadUserRoles执行到userRoleMockDB.Find(q)时,直接触发nil接口方法调用的panic,Echo框架未捕获panic的情况下会直接断开连接,导致前端收到空响应。 - 你当前定义的
CollectionAPI接口无法实现单元测试的数据库隔离:Find方法返回的是mgo驱动的具体结构体*mgo.Query而非自定义接口,即使构造Mock实现,后续调用All方法时依然会走真实mgo的数据库操作逻辑,达不到Mock效果。
修复方案
1. 补全生产环境的接口变量赋值逻辑
不要单独声明Mock专用的全局变量,统一用同一个接口类型全局变量,生产环境初始化时赋值真实mgo集合实例,仅测试场景下替换为Mock实现:
// 全局数据库实例,可持有真实mgo集合或Mock实现 var userRoleDB dbinterface.CollectionAPI // 服务启动时调用的数据库初始化方法 func InitMongo() error { session, err := mgo.Dial("你的Mongo连接串") if err != nil { return err } // 给接口变量赋值真实的*mgo.Collection实例,保证业务正常运行 userRoleDB = session.DB("你的业务库名").C("user_roles") return nil }
服务启动时先调用InitMongo()完成初始化,再启动HTTP服务,即可避免nil调用panic。
2. 重构接口抽象,支持链式调用Mock
你需要把Find返回的Query对象也抽象为接口,才能完全隔离mgo驱动的真实逻辑:
// 抽象mgo.Query的核心方法,满足当前业务调用需求即可 type QueryAPI interface { All(result interface{}) error // 后续如果用到One、Count等方法,就在这里继续追加定义 } type CollectionAPI interface { Find(query interface{}) QueryAPI }
真实的*mgo.Collection和*mgo.Query本身就实现了这两个接口的方法,不需要额外做适配,生产环境可以直接赋值。
3. 编写Mock实现与单元测试
手写测试用的Mock结构体,自定义方法返回逻辑:
// Mock集合实现 type mockCollection struct { findFunc func(query interface{}) QueryAPI } func (m *mockCollection) Find(query interface{}) QueryAPI { return m.findFunc(query) } // Mock Query实现 type mockQuery struct { allFunc func(result interface{}) error } func (m *mockQuery) All(result interface{}) error { return m.allFunc(result) }
单元测试中替换全局变量为Mock实例,测试结束后还原,避免影响其他用例:
func TestLoadUserRoles(t *testing.T) { // 保存原全局变量值,测试结束还原 originDB := userRoleDB defer func() { userRoleDB = originDB }() // 构造测试预期数据 expected := []UserRoles{ {Username: "test_user", Role: "viewer"}, {Username: "test_user", Role: "editor"}, } // 注入Mock userRoleDB = &mockCollection{ findFunc: func(query interface{}) QueryAPI { // 可在这里校验查询参数是否符合预期 q := query.(bson.M) if q["username"] != "test_user" { t.Errorf("查询参数错误,预期username为test_user,实际为%v", q["username"]) } return &mockQuery{ allFunc: func(result interface{}) error { // 将预期数据写入结果指针,模拟数据库返回 *result.(*[]UserRoles) = expected return nil }, } }, } // 执行被测函数 res, err := LoadUserRoles("test_user") if err != nil { t.Fatalf("执行失败,错误: %v", err) } if len(res) != 2 { t.Fatalf("返回结果长度错误,预期2条,实际%d条", len(res)) } }
优化建议
长期来看不推荐用全局变量存储数据库依赖,更好的实践是将CollectionAPI作为依赖注入到对应业务结构体或函数参数中,从根源上避免全局变量篡改、测试环境污染的问题。如果手写Mock效率低,可以用gomock类工具根据接口定义自动生成Mock代码。
内容的提问来源于stack exchange,提问作者Sanaz Movahed
相关产品推荐
相关产品推荐

