sqlmock测试遇database is closed问题:不修改生产代码如何解决?
解决测试中数据库连接被提前关闭的问题
问题核心在于生产环境与测试环境的差异:
- 生产代码里
openDB调用sql.Open返回的是连接池,调用Close()只是关闭整个连接池,但每次openDB都会创建新的连接池,所以两次调用互不影响。 - 但测试中你复用了同一个
sqlmock生成的*sql.DB实例,第一次doSomeQuery里的defer db.Close()直接关闭了这个唯一的实例,第二次查询自然会报错database is closed。
不修改生产代码的解决方案
创建一个包装类型,覆盖Close()方法,让它不执行实际的关闭操作,这样就能保留底层的sqlmock连接供多次查询使用:
// 定义包装类型,嵌入*sql.DB以继承其他方法 type mockDBWrapper struct { *sql.DB } // 重写Close方法,不执行实际关闭 func (m *mockDBWrapper) Close() error { // 这里可以留空,或者添加日志/计数用于测试验证 return nil }
然后在测试中修改openDB的实现,返回包装后的实例:
func TestSomeTestName(t *testing.T) { db, mock, err := sqlmock.New() if err != nil { t.Fatalf("failed to create sqlmock: %v", err) } defer db.Close() // 替换全局的openDB,返回包装后的mock DB openDB = func() *sql.DB { return &mockDBWrapper{db} } // 设置查询期望(根据实际SQL调整) mock.ExpectQuery(`SELECT EXISTS\(SELECT 1 FROM your_table WHERE id = \?\)`). WithArgs("run1"). WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true)) mock.ExpectQuery(`SELECT EXISTS\(SELECT 1 FROM your_table WHERE id = \?\)`). WithArgs("run2"). WillReturnRows(sqlmock.NewRows([]string{"exists"}).AddRow(true)) res1, res2 := toTest() // 验证结果 assert.True(t, res1) assert.True(t, res2) // 验证所有mock期望都被满足 if err := mock.ExpectationsWereMet(); err != nil { t.Errorf("unfulfilled mock expectations: %v", err) } }
补充说明
- 这个包装类型只需要覆盖
Close()方法,其他方法(如QueryRow)会自动继承嵌入的*sql.DB的实现,完全兼容生产代码的调用逻辑。 - 如果需要验证生产代码中确实调用了
Close(),可以在mockDBWrapper.Close()里加一个计数器,测试结束后检查计数是否为2,确保资源释放逻辑正常。
内容的提问来源于stack exchange,提问作者Dmitry Malys
相关产品推荐
相关产品推荐

