使用Golang进行单元测试:gocb实现Couchbase Mocking最佳实践

使用Golang进行单元测试:gocb实现Couchbase Mocking最佳实践 我正在当前Go项目中使用以下Couchbase包: “gopkg.in/couchbase/gocb.v1

type DbRepository interface {
	Insert(item *entity.SampleEntity) error
}

//repository_couchbase.go

cbConnStr = config.Viper.GetString(
“database.couchbase.connectionstring”)
cbBucket = config.Viper.GetString(
“database.couchbase.bucket”)
)

type CbRepository struct {
Cluster *gocb.Cluster
Bucket *gocb.Bucket
}

func NewCbRepository() *CbRepository {
logger.BootstrapLogger.Debug(“Entering Repository.NewCbRepository() …”)

cluster, err := gocb.Connect(cbConnStr)
if err != nil {
	logger.BootstrapLogger.Error(err)
	panic(err)
}
cluster.Authenticate(gocb.PasswordAuthenticator{
	Username: config.Viper.GetString("database.couchbase.username"),
	Password: config.Viper.GetString("database.couchbase.password"),
})

bucket, err := cluster.OpenBucket(cbBucket, "")
if err != nil {
	logger.BootstrapLogger.Error(err)
	panic(err)
}
return &CbRepository{
	Cluster: cluster,
	Bucket:  bucket,
}

}

func (r CbRepository) Insert(item entity.Favorite) error {
logger.Logger.Debug(“Entering CbRepository.Insert() …”)
_, err := r.Bucket.Insert(“DocumentName”, item, 0)
if err != nil {
logger.Logger.Error(err)
switch err {
/
* Include -case gocb.Err**- to handle Couchbase error types here as required.
* Fallback to default if none match
*/
case gocb.ErrTimeout:
return entity.ErrDatabaseFailure
case gocb.ErrKeyExists:
return entity.ErrItemExists
default:
return entity.ErrDefault
}
}
return nil
}

注意:存储库可以有多种类型,比如Couchbase或AWS Documentdb。这就是为什么我为repositoryCb单独实现的原因。

这个Couchbase包不是基于接口设计的,那么我该如何模拟数据库请求? r.Bucket.Insert() — r.Bucket.Upsert() 等

我的程序结构如下:

  1. Main > Handler > Service层 > repositoryCb > repository

使用mockGen, 我已在服务层模拟了存储库接口。 在处理器层模拟了服务层。

我的问题是如何模拟核心CRUD操作,比如bucket.Insert()、bucket.Upsert()、bucket.Get()等。

请分享您的想法。


更多关于使用Golang进行单元测试:gocb实现Couchbase Mocking最佳实践的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于使用Golang进行单元测试:gocb实现Couchbase Mocking最佳实践的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


对于模拟Couchbase的gocb操作,推荐使用接口包装和依赖注入的方式。以下是具体的实现方案:

首先,创建一个包装gocb.Bucket操作的接口:

type BucketOperations interface {
    Insert(key string, value interface{}, expiry uint32) (gocb.Cas, error)
    Upsert(key string, value interface{}, expiry uint32) (gocb.Cas, error)
    Get(key string, valuePtr interface{}) (gocb.Cas, error)
    Remove(key string, cas gocb.Cas) (gocb.Cas, error)
}

type CbRepository struct {
    BucketOps BucketOperations
}

func NewCbRepository(bucketOps BucketOperations) *CbRepository {
    return &CbRepository{
        BucketOps: bucketOps,
    }
}

然后修改实际的Couchbase实现:

type RealBucket struct {
    Bucket *gocb.Bucket
}

func (r *RealBucket) Insert(key string, value interface{}, expiry uint32) (gocb.Cas, error) {
    return r.Bucket.Insert(key, value, expiry)
}

func (r *RealBucket) Upsert(key string, value interface{}, expiry uint32) (gocb.Cas, error) {
    return r.Bucket.Upsert(key, value, expiry)
}

func (r *RealBucket) Get(key string, valuePtr interface{}) (gocb.Cas, error) {
    return r.Bucket.Get(key, valuePtr)
}

func (r *RealBucket) Remove(key string, cas gocb.Cas) (gocb.Cas, error) {
    return r.Bucket.Remove(key, cas)
}

// 修改原有的构造函数
func NewRealCbRepository() *CbRepository {
    // ... 原有的连接代码
    realBucket := &RealBucket{Bucket: bucket}
    return &CbRepository{
        BucketOps: realBucket,
    }
}

现在创建mock实现用于测试:

type MockBucket struct {
    InsertFunc func(key string, value interface{}, expiry uint32) (gocb.Cas, error)
    UpsertFunc func(key string, value interface{}, expiry uint32) (gocb.Cas, error)
    GetFunc    func(key string, valuePtr interface{}) (gocb.Cas, error)
    RemoveFunc func(key string, cas gocb.Cas) (gocb.Cas, error)
}

func (m *MockBucket) Insert(key string, value interface{}, expiry uint32) (gocb.Cas, error) {
    if m.InsertFunc != nil {
        return m.InsertFunc(key, value, expiry)
    }
    return gocb.Cas(0), nil
}

func (m *MockBucket) Upsert(key string, value interface{}, expiry uint32) (gocb.Cas, error) {
    if m.UpsertFunc != nil {
        return m.UpsertFunc(key, value, expiry)
    }
    return gocb.Cas(0), nil
}

func (m *MockBucket) Get(key string, valuePtr interface{}) (gocb.Cas, error) {
    if m.GetFunc != nil {
        return m.GetFunc(key, valuePtr)
    }
    return gocb.Cas(0), nil
}

func (m *MockBucket) Remove(key string, cas gocb.Cas) (gocb.Cas, error) {
    if m.RemoveFunc != nil {
        return m.RemoveFunc(key, cas)
    }
    return gocb.Cas(0), nil
}

在单元测试中的使用示例:

func TestCbRepository_Insert(t *testing.T) {
    mockBucket := &MockBucket{
        InsertFunc: func(key string, value interface{}, expiry uint32) (gocb.Cas, error) {
            if key == "existing_key" {
                return gocb.Cas(0), gocb.ErrKeyExists
            }
            return gocb.Cas(123), nil
        },
    }
    
    repo := NewCbRepository(mockBucket)
    entity := &entity.SampleEntity{ID: "test", Name: "Test Entity"}
    
    err := repo.Insert(entity)
    
    if err != nil {
        t.Errorf("Expected no error, got %v", err)
    }
}

func TestCbRepository_Insert_KeyExists(t *testing.T) {
    mockBucket := &MockBucket{
        InsertFunc: func(key string, value interface{}, expiry uint32) (gocb.Cas, error) {
            return gocb.Cas(0), gocb.ErrKeyExists
        },
    }
    
    repo := NewCbRepository(mockBucket)
    entity := &entity.SampleEntity{ID: "existing_key", Name: "Test Entity"}
    
    err := repo.Insert(entity)
    
    if err != entity.ErrItemExists {
        t.Errorf("Expected ErrItemExists, got %v", err)
    }
}

修改原有的Insert方法实现:

func (r CbRepository) Insert(item entity.Favorite) error {
    logger.Logger.Debug("Entering CbRepository.Insert() ...")
    _, err := r.BucketOps.Insert("DocumentName", item, 0)
    if err != nil {
        logger.Logger.Error(err)
        switch err {
        case gocb.ErrTimeout:
            return entity.ErrDatabaseFailure
        case gocb.ErrKeyExists:
            return entity.ErrItemExists
        default:
            return entity.ErrDefault
        }
    }
    return nil
}

这种方法通过接口抽象了具体的Couchbase操作,使得在测试时可以轻松注入mock实现,同时保持生产代码使用真实的Couchbase连接。

回到顶部