预期查询部分中的 go-sqlmock 测试问题
Issue with go-sqlmock testing in the expected query part
我是第一次使用 go-sqlmock
,我正在尝试为 post 操作编写测试。我正在使用 gorm
和 gin
.
- 测试给我一个错误
s.mock.ExpectQuery(regexp.QuoteMeta(....
我不是这里的问题所在。我已经 post 编辑了测试和输出。
- 另外,(这与1无关)在这个测试中我真的不知道
code
会是什么,因为它是在api控制器中随机生成的。有没有办法在 code
字段中分配通用编号。
测试文件
package unit
import (
"net/http"
"net/http/httptest"
"regexp"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/SamiAlsubhi/go/controllers"
"github.com/SamiAlsubhi/go/routes"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type Suite struct {
suite.Suite
DB *gorm.DB
mock sqlmock.Sqlmock
router *gin.Engine
}
func (s *Suite) SetupSuite(t *testing.T) {
conn, mock, err := sqlmock.New()
if err != nil || conn == nil {
t.Errorf("Failed to open mock sql db, got error: %v", err)
}
s.mock = mock
dialector := postgres.New(postgres.Config{
DSN: "sqlmock_db_0",
DriverName: "postgres",
Conn: conn,
PreferSimpleProtocol: true,
})
if db, err := gorm.Open(dialector, &gorm.Config{}); err != nil || db == nil {
t.Errorf("Failed to open gorm v2 db, got error: %v", err)
} else {
s.DB = db
}
api := &controllers.API{Db: s.DB}
s.router = routes.SetupRouter(api)
}
func TestSetup(t *testing.T) {
suite.Run(t, new(Suite))
}
func (s *Suite) AfterTest(_, _ string) {
require.NoError(s.T(), s.mock.ExpectationsWereMet())
}
func (s *Suite) Test_GetOTP() {
var (
phone = "99999999"
code = "123456"
)
s.mock.ExpectQuery(regexp.QuoteMeta(
`INSERT INTO "otps" ("phone","code") VALUES (,) RETURNING "otps"."id"`)).
WithArgs(phone, code).
WillReturnRows(sqlmock.NewRows([]string{"id"}).
AddRow(1))
s.mock.ExpectCommit()
w := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/api/auth/get-otp/"+phone, nil)
require.NoError(s.T(), err)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), 200, w.Code)
//require.Nil(s.T(), deep.Equal(&model.Person{ID: id, Name: name}, w.Body))
}
输出。
--- FAIL: TestSetup (0.00s)
--- FAIL: TestSetup/Test_GetOTP (0.00s)
/Users/sami/Desktop/SamiAlsubhi/go/test/unit/suite.go:63: test panicked: runtime error: invalid memory address or nil pointer dereference
goroutine 26 [running]:
runtime/debug.Stack()
/usr/local/go/src/runtime/debug/stack.go:24 +0x65
github.com/stretchr/testify/suite.failOnPanic(0xc000001a00)
/Users/sami/Desktop/golang/pkg/mod/github.com/stretchr/testify@v1.7.0/suite/suite.go:63 +0x3e
panic({0x49e96a0, 0x5193810})
/usr/local/go/src/runtime/panic.go:1038 +0x215
github.com/SamiAlsubhi/go/test/unit.(*Suite).AfterTest(0x4abe61b, {0x4becfd0, 0xc000468940}, {0x0, 0x0})
/Users/sami/Desktop/SamiAlsubhi/go/test/unit/setup_test.go:60 +0x1c
github.com/stretchr/testify/suite.Run.func1.1()
/Users/sami/Desktop/golang/pkg/mod/github.com/stretchr/testify@v1.7.0/suite/suite.go:137 +0x1b7
panic({0x49e96a0, 0x5193810})
/usr/local/go/src/runtime/panic.go:1038 +0x215
github.com/SamiAlsubhi/go/test/unit.(*Suite).Test_GetOTP(0xc000468940)
/Users/sami/Desktop/SamiAlsubhi/go/test/unit/setup_test.go:69 +0x4f
reflect.Value.call({0xc000049140, 0xc000010308, 0x13}, {0x4abf50c, 0x4}, {0xc000080e70, 0x1, 0x1})
/usr/local/go/src/reflect/value.go:543 +0x814
reflect.Value.Call({0xc000049140, 0xc000010308, 0xc000468940}, {0xc0003c9e70, 0x1, 0x1})
/usr/local/go/src/reflect/value.go:339 +0xc5
github.com/stretchr/testify/suite.Run.func1(0xc000001a00)
/Users/sami/Desktop/golang/pkg/mod/github.com/stretchr/testify@v1.7.0/suite/suite.go:158 +0x4b6
testing.tRunner(0xc000001a00, 0xc000162000)
/usr/local/go/src/testing/testing.go:1259 +0x102
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:1306 +0x35a
FAIL
coverage: [no statements]
FAIL github.com/SamiAlsubhi/go/test/unit 0.912s
FAIL
第一个问题的解决方案:
使用 testify/suite
时,如果为 Suite
结构创建了一堆方法,它们将在 运行 测试时自动执行。也就是说,这些方法将通过接口过滤器。在 .SetupSuite
的情况下,它必须没有参数并且没有 return,以便 运行.
第二个问题的解决方法:
go-sqlmock
中有一种方法可以通过使用 sqlmock.AnyArg()
.
来匹配任何类型的数据
固定码:
package unit
import (
"encoding/json"
"fmt"
"math/rand"
"net/http"
"net/http/httptest"
"regexp"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/SamiAlsubhi/go/controllers"
"github.com/SamiAlsubhi/go/routes"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type Suite struct {
suite.Suite
DB *gorm.DB
mock sqlmock.Sqlmock
router *gin.Engine
}
func (s *Suite) SetupSuite() {
//t.Logf("setup start")
conn, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp))
if err != nil || conn == nil {
panic(fmt.Sprintf("Failed to open mock sql db, got error: %v", err))
}
s.mock = mock
dialector := postgres.New(postgres.Config{
DSN: "sqlmock_db_0",
DriverName: "postgres",
Conn: conn,
PreferSimpleProtocol: true,
})
if db, err := gorm.Open(dialector, &gorm.Config{SkipDefaultTransaction: true}); err != nil || db == nil {
panic(fmt.Sprintf("Failed to open gorm v2 db, got error: %v", err))
} else {
s.DB = db
}
api := &controllers.API{Db: s.DB, IsTesting: true}
s.router = routes.SetupRouter(api)
}
func TestSetup(t *testing.T) {
suite.Run(t, new(Suite))
}
// func (s *Suite) AfterTest(_, _ string) {
// require.NoError(s.T(), s.mock.ExpectationsWereMet())
// }
func (s *Suite) Test_GetOTP_Non_Existing_Phone() {
/*
This to test getting OTP for a phone number that does not exist in the otps table
*/
phone := fmt.Sprintf("%v", 90000000+rand.Intn(99999999-90000000))
s.mock.MatchExpectationsInOrder(false)
s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "otps" WHERE phone = AND "otps"."deleted_at" IS NULL`)).
WithArgs(phone).
WillReturnRows(sqlmock.NewRows([]string{"count"}).
AddRow(0))
s.mock.ExpectQuery(regexp.QuoteMeta(
`INSERT INTO "otps" ("created_at","updated_at","deleted_at","phone","code") VALUES (,,,,) RETURNING "id"`)).
WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), phone, sqlmock.AnyArg()).
WillReturnRows(sqlmock.NewRows([]string{"id"}).
AddRow(1))
w := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/api/auth/get-otp/"+phone, nil)
require.NoError(s.T(), err)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), 200, w.Code)
//parse response
var response gin.H
err = json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(s.T(), err)
_, ok := response["expiry_in"]
assert.True(s.T(), ok)
require.NoError(s.T(), s.mock.ExpectationsWereMet())
}
我是第一次使用 go-sqlmock
,我正在尝试为 post 操作编写测试。我正在使用 gorm
和 gin
.
- 测试给我一个错误
s.mock.ExpectQuery(regexp.QuoteMeta(....
我不是这里的问题所在。我已经 post 编辑了测试和输出。 - 另外,(这与1无关)在这个测试中我真的不知道
code
会是什么,因为它是在api控制器中随机生成的。有没有办法在code
字段中分配通用编号。
测试文件
package unit
import (
"net/http"
"net/http/httptest"
"regexp"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/SamiAlsubhi/go/controllers"
"github.com/SamiAlsubhi/go/routes"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type Suite struct {
suite.Suite
DB *gorm.DB
mock sqlmock.Sqlmock
router *gin.Engine
}
func (s *Suite) SetupSuite(t *testing.T) {
conn, mock, err := sqlmock.New()
if err != nil || conn == nil {
t.Errorf("Failed to open mock sql db, got error: %v", err)
}
s.mock = mock
dialector := postgres.New(postgres.Config{
DSN: "sqlmock_db_0",
DriverName: "postgres",
Conn: conn,
PreferSimpleProtocol: true,
})
if db, err := gorm.Open(dialector, &gorm.Config{}); err != nil || db == nil {
t.Errorf("Failed to open gorm v2 db, got error: %v", err)
} else {
s.DB = db
}
api := &controllers.API{Db: s.DB}
s.router = routes.SetupRouter(api)
}
func TestSetup(t *testing.T) {
suite.Run(t, new(Suite))
}
func (s *Suite) AfterTest(_, _ string) {
require.NoError(s.T(), s.mock.ExpectationsWereMet())
}
func (s *Suite) Test_GetOTP() {
var (
phone = "99999999"
code = "123456"
)
s.mock.ExpectQuery(regexp.QuoteMeta(
`INSERT INTO "otps" ("phone","code") VALUES (,) RETURNING "otps"."id"`)).
WithArgs(phone, code).
WillReturnRows(sqlmock.NewRows([]string{"id"}).
AddRow(1))
s.mock.ExpectCommit()
w := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/api/auth/get-otp/"+phone, nil)
require.NoError(s.T(), err)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), 200, w.Code)
//require.Nil(s.T(), deep.Equal(&model.Person{ID: id, Name: name}, w.Body))
}
输出。
--- FAIL: TestSetup (0.00s)
--- FAIL: TestSetup/Test_GetOTP (0.00s)
/Users/sami/Desktop/SamiAlsubhi/go/test/unit/suite.go:63: test panicked: runtime error: invalid memory address or nil pointer dereference
goroutine 26 [running]:
runtime/debug.Stack()
/usr/local/go/src/runtime/debug/stack.go:24 +0x65
github.com/stretchr/testify/suite.failOnPanic(0xc000001a00)
/Users/sami/Desktop/golang/pkg/mod/github.com/stretchr/testify@v1.7.0/suite/suite.go:63 +0x3e
panic({0x49e96a0, 0x5193810})
/usr/local/go/src/runtime/panic.go:1038 +0x215
github.com/SamiAlsubhi/go/test/unit.(*Suite).AfterTest(0x4abe61b, {0x4becfd0, 0xc000468940}, {0x0, 0x0})
/Users/sami/Desktop/SamiAlsubhi/go/test/unit/setup_test.go:60 +0x1c
github.com/stretchr/testify/suite.Run.func1.1()
/Users/sami/Desktop/golang/pkg/mod/github.com/stretchr/testify@v1.7.0/suite/suite.go:137 +0x1b7
panic({0x49e96a0, 0x5193810})
/usr/local/go/src/runtime/panic.go:1038 +0x215
github.com/SamiAlsubhi/go/test/unit.(*Suite).Test_GetOTP(0xc000468940)
/Users/sami/Desktop/SamiAlsubhi/go/test/unit/setup_test.go:69 +0x4f
reflect.Value.call({0xc000049140, 0xc000010308, 0x13}, {0x4abf50c, 0x4}, {0xc000080e70, 0x1, 0x1})
/usr/local/go/src/reflect/value.go:543 +0x814
reflect.Value.Call({0xc000049140, 0xc000010308, 0xc000468940}, {0xc0003c9e70, 0x1, 0x1})
/usr/local/go/src/reflect/value.go:339 +0xc5
github.com/stretchr/testify/suite.Run.func1(0xc000001a00)
/Users/sami/Desktop/golang/pkg/mod/github.com/stretchr/testify@v1.7.0/suite/suite.go:158 +0x4b6
testing.tRunner(0xc000001a00, 0xc000162000)
/usr/local/go/src/testing/testing.go:1259 +0x102
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:1306 +0x35a
FAIL
coverage: [no statements]
FAIL github.com/SamiAlsubhi/go/test/unit 0.912s
FAIL
第一个问题的解决方案:
使用 testify/suite
时,如果为 Suite
结构创建了一堆方法,它们将在 运行 测试时自动执行。也就是说,这些方法将通过接口过滤器。在 .SetupSuite
的情况下,它必须没有参数并且没有 return,以便 运行.
第二个问题的解决方法:
go-sqlmock
中有一种方法可以通过使用 sqlmock.AnyArg()
.
固定码:
package unit
import (
"encoding/json"
"fmt"
"math/rand"
"net/http"
"net/http/httptest"
"regexp"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/SamiAlsubhi/go/controllers"
"github.com/SamiAlsubhi/go/routes"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type Suite struct {
suite.Suite
DB *gorm.DB
mock sqlmock.Sqlmock
router *gin.Engine
}
func (s *Suite) SetupSuite() {
//t.Logf("setup start")
conn, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp))
if err != nil || conn == nil {
panic(fmt.Sprintf("Failed to open mock sql db, got error: %v", err))
}
s.mock = mock
dialector := postgres.New(postgres.Config{
DSN: "sqlmock_db_0",
DriverName: "postgres",
Conn: conn,
PreferSimpleProtocol: true,
})
if db, err := gorm.Open(dialector, &gorm.Config{SkipDefaultTransaction: true}); err != nil || db == nil {
panic(fmt.Sprintf("Failed to open gorm v2 db, got error: %v", err))
} else {
s.DB = db
}
api := &controllers.API{Db: s.DB, IsTesting: true}
s.router = routes.SetupRouter(api)
}
func TestSetup(t *testing.T) {
suite.Run(t, new(Suite))
}
// func (s *Suite) AfterTest(_, _ string) {
// require.NoError(s.T(), s.mock.ExpectationsWereMet())
// }
func (s *Suite) Test_GetOTP_Non_Existing_Phone() {
/*
This to test getting OTP for a phone number that does not exist in the otps table
*/
phone := fmt.Sprintf("%v", 90000000+rand.Intn(99999999-90000000))
s.mock.MatchExpectationsInOrder(false)
s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "otps" WHERE phone = AND "otps"."deleted_at" IS NULL`)).
WithArgs(phone).
WillReturnRows(sqlmock.NewRows([]string{"count"}).
AddRow(0))
s.mock.ExpectQuery(regexp.QuoteMeta(
`INSERT INTO "otps" ("created_at","updated_at","deleted_at","phone","code") VALUES (,,,,) RETURNING "id"`)).
WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), phone, sqlmock.AnyArg()).
WillReturnRows(sqlmock.NewRows([]string{"id"}).
AddRow(1))
w := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/api/auth/get-otp/"+phone, nil)
require.NoError(s.T(), err)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), 200, w.Code)
//parse response
var response gin.H
err = json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(s.T(), err)
_, ok := response["expiry_in"]
assert.True(s.T(), ok)
require.NoError(s.T(), s.mock.ExpectationsWereMet())
}