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

如何对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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.08 17:40:23