如何对GORM V2的Storage.Open方法进行单元测试(含Mock)
问题描述
我发现大量关于GORM Mock的问题都是针对旧版V1包(github.com/jinzhu/gorm),且多使用github.com/DATA-DOG/go-sqlmock,但关于V2的相关资料极少。我编写了如下storage包代码:
... type Storage struct { GormDB *gorm.DB SqlDB *sql.DB mutex sync.Mutex ReadTimeout int WriteTimeout int } func (ps *Storage) Open(settings *Settings) error { if err := settings.Validate(); err != nil { return err } ps.mutex.Lock() defer ps.mutex.Unlock() if ps.GormDB != nil { return nil } gormDB, err := gorm.Open(postgres.New(postgres.Config{ DSN: settings.GetDSN(), }), &gorm.Config{ SkipDefaultTransaction: true, }) if err != nil { return fmt.Errorf("%s: %v", DBConnectError, err) } ps.GormDB = gormDB sqlDB, err := ps.GormDB.DB() if err != nil { return fmt.Errorf("%s: %v", DBRetrievalError, err) } ps.SqlDB = sqlDB ps.SqlDB.SetMaxIdleConns(settings.MaxIdleConnections) ps.SqlDB.SetMaxOpenConns(settings.MaxOpenConnections) ps.ReadTimeout = settings.ReadTimeout ps.WriteTimeout = settings.WriteTimeout return nil }
请问如何对该Open方法进行单元测试,验证gorm.Open是否接收到了预期配置?我不想自行编写GORM接口来实现Mock,也不想通过Docker启动Postgres进行集成测试。此外,如何Mock数据库连接让gorm.Open不返回错误?使用sqlmock.NewWithDSN似乎无法解决该问题。
解决方案
1. 小改代码支持Mock(无需额外接口)
在你的storage包顶部添加一个包级变量,替换直接调用的gorm.Open:
// storage包内新增 var gormOpen = gorm.Open
然后修改Open方法中的调用语句:
gormDB, err := gormOpen(postgres.New(postgres.Config{ DSN: settings.GetDSN(), }), &gorm.Config{ SkipDefaultTransaction: true, })
这个改动侵入性极低,无需定义额外接口,仅通过变量替换实现测试Mock。
2. 编写单元测试:Mock连接+验证配置
在测试用例中,我们可以替换gormOpen为自定义Mock函数,既返回无错误的*gorm.DB,又能检查传入的配置是否符合预期:
import ( "testing" "github.com/DATA-DOG/go-sqlmock" "gorm.io/driver/postgres" "gorm.io/gorm" "github.com/stretchr/testify/assert" ) func TestStorage_Open(t *testing.T) { // 创建sqlmock的模拟DB和mock对象 mockDB, _, err := sqlmock.New() assert.NoError(t, err) defer mockDB.Close() // 基于mockDB构造postgres方言驱动 mockDialector := postgres.New(postgres.Config{ Conn: mockDB, DSN: "sqlmock_placeholder", }) // 备份原gormOpen变量,测试后恢复 originalGormOpen := storage.GormOpen defer func() { storage.GormOpen = originalGormOpen }() // 替换为自定义Mock函数,验证配置并返回模拟gorm.DB storage.GormOpen = func(dialector gorm.Dialector, opts ...gorm.Option) (*gorm.DB, error) { // 验证postgres驱动配置 pgDialector, ok := dialector.(*postgres.Dialector) assert.True(t, ok) assert.Equal(t, "test_expected_dsn", pgDialector.Config.DSN) // 验证gorm全局配置 assert.Len(t, opts, 1) configOpt, ok := opts[0].(gorm.OptionFunc) assert.True(t, ok) testConfig := &gorm.Config{} configOpt(testConfig) assert.True(t, testConfig.SkipDefaultTransaction) // 返回基于mockDB的gorm.DB,避免真实数据库连接 return gorm.Open(mockDialector, &gorm.Config{}) } // 构造测试用的合法Settings对象 testSettings := &storage.Settings{ DSN: "test_expected_dsn", MaxIdleConnections: 5, MaxOpenConnections: 10, ReadTimeout: 30, WriteTimeout: 60, } // 若Settings.Validate()有逻辑,可提前Mock或构造合法数据跳过校验 // 执行测试 s := &storage.Storage{} err = s.Open(testSettings) assert.NoError(t, err) // 验证连接池参数是否正确设置 assert.Equal(t, 5, s.SqlDB.MaxIdleConns()) assert.Equal(t, 10, s.SqlDB.MaxOpenConns()) assert.Equal(t, 30, s.ReadTimeout) assert.Equal(t, 60, s.WriteTimeout) }
3. 核心逻辑说明
- 通过包级变量替换
gorm.Open,无需重构原有代码或定义GORM接口,满足你不想额外编写Mock接口的需求。 - 利用
sqlmock创建的模拟DB,让gorm.Open完全脱离真实数据库,解决连接报错问题。 - 在Mock函数中解析传入的
dialector和gorm.Option,可以精准验证gorm.Open是否接收到预期的DSN、事务配置等参数。
内容的提问来源于stack exchange,提问作者freethinker
相关产品推荐
相关产品推荐

