最近工作中需要写mysql相关单测,但是有个case一直报错,请看如下示意代码
user的model定义代码,包括user结构定义和一个ListUser方法package models
import "gorm.io/gorm"
type User struct {
Id int `gorm:"id"`
Name string `gorm:"name"`
Email string `gorm:"email"`
}
func (User) TableName() string {
return "users"
}
func ListUsers(db \*gorm.DB) ([]User, error) {
var users []User
err := db.Find(&users).Error
return users, err
}
- 下面对ListUsers写一个单测package models
import (
"database/sql"
"strconv"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/stretchr/testify/assert"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
var (
sqlDB \*sql.DB
sqlMock sqlmock.Sqlmock
gormDB \*gorm.DB
)
func setupTestCase(t \*testing.T) func(t \*testing.T) {
var err error
// gorm
sqlDB, sqlMock, err = sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp))
if err != nil {
t.Fatal(err)
}
gormDB, err = gorm.Open(mysql.New(mysql.Config{
SkipInitializeWithVersion: true,
Conn: sqlDB,
}), &gorm.Config{})
if err != nil {
t.Fatal(err)
}
return func(t \*testing.T) {
\_ = sqlDB.Close()
}
}
func TestListUsers(t \*testing.T) {
rows := sqlmock.NewRows([]string{"id", "name", "email"}).AddRow("1", "test", "test@test.com")
for i := 0; i < 2; i++ {
t.Run(strconv.Itoa(i), func(t \*testing.T) {
teardownTestCase := setupTestCase(t)
defer teardownTestCase(t)
sqlMock.ExpectQuery("^SELECT (.+) FROM `users`").WillReturnRows(rows)
users, \_ := ListUsers(gormDB)
assert.Equal(t, 1, len(users))
})
}
}
单测总体也比较简单,主要看TestListUsers
方法,在这个方法里
由于我们的ListUsers就是一个简单的Selct,所以ExpectQuery一定匹配上
预期在2个循环周期内,ListUsers都返回第一步定位的rows,断言成功
但是实际上,第一次断言成功,第二次失败
/usr/local/go1.22.3/bin/go tool test2json -t ... -test.run ^\QTestListUsers\E$
=== RUN TestListUsers
=== RUN TestListUsers/0
--- PASS: TestListUsers/0 (0.00s)
=== RUN TestListUsers/1
user_test.go:52:
Error Trace: ../models/user_test.go:52
Error: Not equal:
expected: 1
actual : 0
Test: TestListUsers/1
--- FAIL: TestListUsers/1 (0.00s)
预期:1
实际:0
为每次ExepectQuery都创建单独的rows即可
TestListUsers修改如下
func TestListUsers(t *testing.T) {
for i := 0; i < 2; i++ {
t.Run(strconv.Itoa(i), func(t *testing.T) {
teardownTestCase := setupTestCase(t)
defer teardownTestCase(t)
rows := sqlmock.NewRows([]string{"id", "name", "email"}).AddRow("1", "test", "test@test.com")
sqlMock.ExpectQuery("^SELECT (.+) FROM `users`").WillReturnRows(rows)
users, _ := ListUsers(gormDB)
assert.Equal(t, 1, len(users))
})
}
}
首先我们来看sqlmock.Row的结构体
// Rows is a mocked collection of rows to
// return for Query result
type Rows struct {
converter driver.ValueConverter
cols []string
def []*Column
rows [][]driver.Value
pos int
nextErr map[int]error
closeErr error
}
发现这个结构体里有个pos字段,那是不是因为第一次读取后,修改了pos字段,导致第二次读取的时候就读不到内容呢,我们继续看
Rows结构体有个Next方法,这个看接口说明就是用来读取数据的,然后我们看到这里面的确是会修改pos
// advances to next row
func (rs *rowSets) Next(dest []driver.Value) error {
r := rs.sets[rs.pos]
r.pos++
rs.invalidateRaw()
if r.pos > len(r.rows) {
return io.EOF // per interface spec
}
for i, col := range r.rows[r.pos-1] {
if b, ok := rawBytes(col); ok {
rs.raw = append(rs.raw, b)
dest[i] = b
continue
}
dest[i] = col
}
return r.nextErr[r.pos-1]
}
这个函数的大概逻辑就是根据pos的位置,返回rows里的row
sqlmock.Rows对象不要复用,每次都需要重现创建一个
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。