You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.12 19:50:23