database - 在没有库的情况下如何模拟数据库调用?

标签 database go mocking

我一直在努力研究单元测试、依赖注入(inject)、tdd 和所有这些东西,例如,我一直专注于测试进行数据库调用的函数。

假设您有一个 PostgresStore 结构,它接受一个数据库接口(interface),该接口(interface)具有 Query() 方法。

type PostgresStore struct {
    db Database
}

type Database interface {
    Query(query string, args ...interface{}) (*sql.Rows, error)
}

并且您的 PostgresStore 有一个 GetPatients 方法,该方法调用数据库查询。

func (p *PostgresStore) GetPatients() ([]Patient, error) {
    rows, err := p.db.Query("SELECT id, name, age, insurance FROM patients")
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    items := []Patient{}
    for rows.Next() {
        var i Patient
        if err := rows.Scan(
            &i.ID,
            &i.Name,
            &i.Surname,
            &i.Age,
            &i.InsuranceCompany,
        ); err != nil {
            return nil, err
        }
        items = append(items, i)
    }
    if err := rows.Close(); err != nil {
        return nil, err
    }
    if err := rows.Err(); err != nil {
        return nil, err
    }
    return items, nil
}

在真正的实现中,您只需传递 *sql.DB 作为数据库参数,但是你们如何使用假数据库结构编写单元测试?

最佳答案

让我尝试澄清您的一些疑虑。首先,我将分享一个工作示例,以更好地理解正在发生的事情。然后,我将提及所有相关方面。

repo/db.go

package repo

import "database/sql"

type Patient struct {
    ID               int
    Name             string
    Surname          string
    Age              int
    InsuranceCompany string
}

type PostgresStore struct {
    // rely on the generic DB provided by the "sql" package
    db *sql.DB
}

func (p *PostgresStore) GetPatient(id int) ([]Patient, error) {
    rows, err := p.db.Query("SELECT id, name, age, insurance FROM patients")
    if err != nil {
        return nil, err
    }
    defer rows.Close()
    items := []Patient{}
    for rows.Next() {
        var i Patient
        if err := rows.Scan(
            &i.ID,
            &i.Name,
            &i.Surname,
            &i.Age,
            &i.InsuranceCompany,
        ); err != nil {
            return nil, err
        }
        items = append(items, i)
    }
    if err := rows.Close(); err != nil {
        return nil, err
    }
    if err := rows.Err(); err != nil {
        return nil, err
    }
    return items, nil
}

这里,唯一相关的更改是如何定义 PostgresStore 结构。作为db字段,您应该依赖Go标准库的database/sql包提供的通用DB。正因为如此,用假的实现替换它的实现是微不足道的,我们稍后会看到。

Please note that in the GetPatient method you're accepting an id parameter but you're not using it. Your query is more suitable to a method like GetAllPatients or something like that. Be sure to fix it accordingly.

repo/db_test.go

package repo

import (
    "testing"

    "github.com/DATA-DOG/go-sqlmock"
    "github.com/stretchr/testify/assert"
)

func TestGetPatient(t *testing.T) {
    // 1. set up fake db and mock
    db, mock, err := sqlmock.New()
    if err != nil {
        t.Fatalf("err not expected: %v", err)
    }

    // 2. configure the mock. What we expect (query or command)? The outcome (error vs no error).
    rows := sqlmock.NewRows([]string{"id", "name", "surname", "age", "insurance"}).AddRow(1, "john", "doe", 23, "insurance-test")
    mock.ExpectQuery("SELECT id, name, age, insurance FROM patients").WillReturnRows(rows)

    // 3. instantiate the PostgresStore with the fake db
    sut := &PostgresStore{
        db: db,
    }

    // 4. invoke the action we've to test
    got, err := sut.GetPatient(1)

    // 5. assert the result
    assert.Nil(t, err)
    assert.Contains(t, got, Patient{1, "john", "doe", 23, "insurance-test"})
}

这里有很多内容需要介绍。首先,您可以检查代码中的注释,以便您更好地了解每个步骤。在代码中,我们依赖于 github.com/DATA-DOG/go-sqlmock 包,它允许我们轻松模拟数据库客户端。

显然,这段代码的目的是给出如何实现您的需求的总体思路。它可以用更好的方式编写,但它可以成为在此场景中编写测试的良好起点。

请告诉我这是否有帮助,谢谢!

关于database - 在没有库的情况下如何模拟数据库调用?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/75373245/

相关文章:

go - 如何将 reflect.New 的返回值转换回原始类型

go - 为整个项目生成 godoc 文档?

go - 为什么解码此 API 响应会返回意外的 EOF?

c# - 使用模拟进行单元测试。测试行为而不是执行

c# - FakeItEasy 配置 fake 以抛出异常并在下一次调用时返回值

java - c3p0 和 Heroku postgres,SSL 问题

PHP 无法执行查询

mysql - 尝试将图片插入sql数据库

java - 数据库连接池的用途?

unit-testing - 为什么我们使用接口(interface)来模拟 Golang 方法