使用 go-sqlmock 创建 gorm 数据库(运行时错误)
Creating a gorm database with go-sqlmock (runtime error)
总结
我正在尝试使用 go-sqlmock
和 gorm
进行测试。我想为初始数据库迁移编写一个测试,但我遇到了 panic: runtime error: invalid memory address or nil pointer dereference
并且一直无法弄清楚原因。从错误堆栈来看,我认为是这条语句造成的:db.AutoMigrate(&models.User{})
。我不确定为什么,因为据称 db
此时已成功启动并且 models.User
被定义并实例化为 db.AutoMigrate
的参数。我感觉错误出在 mocks.NewDatabase
函数中,但我不知所措。
不确定是否有人有时间或愿意对相关代码进行深入研究并帮助我?我在代码中指出了失败发生的地方(它们在最后两个代码块中)。让我知道是否有任何其他上下文有帮助。
相关代码
project/src/models/models.go
package models
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type Base struct {
ID uuid.UUID `json:"-" gorm:"primaryKey;type:uuid;not null"`
CreatedAt time.Time `json:"-" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"-" gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
}
type User struct {
Base
Name string `json:"-"`
Email string `json:"-" gorm:"unique_index:user_email_index"`
Password string `json:"-" gorm:"size:72"`
}
project/src/mocks/database.go
package mocks
import (
"project/src/models"
"log"
"github.com/DATA-DOG/go-sqlmock"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func NewDatabase() (*gorm.DB, sqlmock.Sqlmock) {
// get db and mock
sqlDB, mock, err := sqlmock.New(
sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp),
)
if err != nil {
log.Fatalf("[sqlmock new] %s", err)
}
defer sqlDB.Close()
// create dialector
dialector := mysql.New(mysql.Config{
Conn: sqlDB,
DriverName: "mysql",
})
// a SELECT VERSION() query will be run when gorm opens the database
// so we need to expect that here
columns := []string{"version"}
mock.ExpectQuery("SELECT VERSION()").WithArgs().WillReturnRows(
mock.NewRows(columns).FromCSVString("1"),
)
// open the database
db, err := gorm.Open(dialector, &gorm.Config{ PrepareStmt: true })
if err != nil {
log.Fatalf("[gorm open] %s", err)
}
return db, mock
}
project/src/database/init.go
package database
import (
"project/src/models"
"gorm.io/gorm"
)
// Init auto-migrates the DB.
func Init(db *gorm.DB) {
// Migrate the schema
// this panics with
// panic: runtime error: invalid memory address or nil pointer dereference
// User is defined and instantiated here
db.AutoMigrate(&models.User{})
}
现在测试:
project/src/database/init_test.go
package database
import (
"project/src/mocks"
"testing"
)
func TestInitMigratesDB(t *testing.T) {
db, mock := mocks.NewDatabase()
mock.ExpectExec("CREATE TABLE users(.*)")
mock.ExpectCommit()
// fails here
Init(db)
}
和日志
Running tool: /usr/local/go/bin/go test -timeout 30s -run ^TestInitMigratesDB$ project/src/database
--- FAIL: TestInitMigratesDB (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x11ce22e]
goroutine 35 [running]:
testing.tRunner.func1.2({0x1505320, 0x19bfb00})
/usr/local/go/src/testing/testing.go:1209 +0x24e
testing.tRunner.func1()
/usr/local/go/src/testing/testing.go:1212 +0x218
panic({0x1505320, 0x19bfb00})
/usr/local/go/src/runtime/panic.go:1038 +0x215
database/sql.(*Rows).close(0x0, {0x0, 0x0})
/usr/local/go/src/database/sql/sql.go:3267 +0x8e
database/sql.(*Rows).Close(0x1e)
/usr/local/go/src/database/sql/sql.go:3263 +0x1d
panic({0x1505320, 0x19bfb00})
/usr/local/go/src/runtime/panic.go:1038 +0x215
database/sql.(*Rows).Next(0x0)
/usr/local/go/src/database/sql/sql.go:2944 +0x27
database/sql.(*Row).Scan(0xc0000afbd8, {0xc0000efb38, 0x11, 0x1})
/usr/local/go/src/database/sql/sql.go:3333 +0xb4
gorm.io/gorm/migrator.Migrator.CurrentDatabase({{0x0, 0xc000483350, {0x1659c58, 0xc00041a0f0}}})
/go/pkg/mod/gorm.io/gorm@v1.21.15/migrator/migrator.go:673 +0x8d
gorm.io/gorm/migrator.Migrator.HasTable.func1(0xc0000f8380)
/go/pkg/mod/gorm.io/gorm@v1.21.15/migrator/migrator.go:265 +0x51
gorm.io/gorm/migrator.Migrator.RunWithValue({{0x80, 0xc000483260, {0x1659c58, 0xc00041a0f0}}}, {0x1512320, 0xc0004fe2a0}, 0xc0000efcb8)
/go/pkg/mod/gorm.io/gorm@v1.21.15/migrator/migrator.go:50 +0x126
gorm.io/gorm/migrator.Migrator.HasTable({{0x0, 0xc000483260, {0x1659c58, 0xc00041a0f0}}}, {0x1512320, 0xc0004fe2a0})
/go/pkg/mod/gorm.io/gorm@v1.21.15/migrator/migrator.go:264 +0xe8
gorm.io/gorm/migrator.Migrator.AutoMigrate({{0x0, 0xc000426f90, {0x1659c58, 0xc00041a0f0}}}, {0xc00040f690, 0x0, 0x0})
/go/pkg/mod/gorm.io/gorm@v1.21.15/migrator/migrator.go:92 +0x127
gorm.io/gorm.(*DB).AutoMigrate(0x151a800, {0xc00040f690, 0x1, 0x1})
/go/pkg/mod/gorm.io/gorm@v1.21.15/migrator.go:26 +0x43
project/src/database.Init(0xc00041c230)
/Projects/project/src/database/init.go:12 +0x7b
project/src/database.TestInitMigratesDB(0x0)
/Projects/project/src/database/init_test.go:12 +0x5a
testing.tRunner(0xc0003a21a0, 0x15b5328)
/usr/local/go/src/testing/testing.go:1259 +0x102
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:1306 +0x35a
FAIL project/src/database 0.276s
FAIL
想通了。这是一个配置选项:&gorm.Config{ PrepareStmt: true }
。虽然这在生产中有效,但它不适用于 sqlmock。通过将其更改为修复它:&gorm.Config{}
.
总结
我正在尝试使用 go-sqlmock
和 gorm
进行测试。我想为初始数据库迁移编写一个测试,但我遇到了 panic: runtime error: invalid memory address or nil pointer dereference
并且一直无法弄清楚原因。从错误堆栈来看,我认为是这条语句造成的:db.AutoMigrate(&models.User{})
。我不确定为什么,因为据称 db
此时已成功启动并且 models.User
被定义并实例化为 db.AutoMigrate
的参数。我感觉错误出在 mocks.NewDatabase
函数中,但我不知所措。
不确定是否有人有时间或愿意对相关代码进行深入研究并帮助我?我在代码中指出了失败发生的地方(它们在最后两个代码块中)。让我知道是否有任何其他上下文有帮助。
相关代码
project/src/models/models.go
package models
import (
"time"
"github.com/google/uuid"
"gorm.io/gorm"
)
type Base struct {
ID uuid.UUID `json:"-" gorm:"primaryKey;type:uuid;not null"`
CreatedAt time.Time `json:"-" gorm:"autoCreateTime"`
UpdatedAt time.Time `json:"-" gorm:"autoUpdateTime"`
DeletedAt gorm.DeletedAt `json:"-" gorm:"index"`
}
type User struct {
Base
Name string `json:"-"`
Email string `json:"-" gorm:"unique_index:user_email_index"`
Password string `json:"-" gorm:"size:72"`
}
project/src/mocks/database.go
package mocks
import (
"project/src/models"
"log"
"github.com/DATA-DOG/go-sqlmock"
"gorm.io/driver/mysql"
"gorm.io/gorm"
)
func NewDatabase() (*gorm.DB, sqlmock.Sqlmock) {
// get db and mock
sqlDB, mock, err := sqlmock.New(
sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp),
)
if err != nil {
log.Fatalf("[sqlmock new] %s", err)
}
defer sqlDB.Close()
// create dialector
dialector := mysql.New(mysql.Config{
Conn: sqlDB,
DriverName: "mysql",
})
// a SELECT VERSION() query will be run when gorm opens the database
// so we need to expect that here
columns := []string{"version"}
mock.ExpectQuery("SELECT VERSION()").WithArgs().WillReturnRows(
mock.NewRows(columns).FromCSVString("1"),
)
// open the database
db, err := gorm.Open(dialector, &gorm.Config{ PrepareStmt: true })
if err != nil {
log.Fatalf("[gorm open] %s", err)
}
return db, mock
}
project/src/database/init.go
package database
import (
"project/src/models"
"gorm.io/gorm"
)
// Init auto-migrates the DB.
func Init(db *gorm.DB) {
// Migrate the schema
// this panics with
// panic: runtime error: invalid memory address or nil pointer dereference
// User is defined and instantiated here
db.AutoMigrate(&models.User{})
}
现在测试:
project/src/database/init_test.go
package database
import (
"project/src/mocks"
"testing"
)
func TestInitMigratesDB(t *testing.T) {
db, mock := mocks.NewDatabase()
mock.ExpectExec("CREATE TABLE users(.*)")
mock.ExpectCommit()
// fails here
Init(db)
}
和日志
Running tool: /usr/local/go/bin/go test -timeout 30s -run ^TestInitMigratesDB$ project/src/database
--- FAIL: TestInitMigratesDB (0.00s)
panic: runtime error: invalid memory address or nil pointer dereference
panic: runtime error: invalid memory address or nil pointer dereference [recovered]
panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x11ce22e]
goroutine 35 [running]:
testing.tRunner.func1.2({0x1505320, 0x19bfb00})
/usr/local/go/src/testing/testing.go:1209 +0x24e
testing.tRunner.func1()
/usr/local/go/src/testing/testing.go:1212 +0x218
panic({0x1505320, 0x19bfb00})
/usr/local/go/src/runtime/panic.go:1038 +0x215
database/sql.(*Rows).close(0x0, {0x0, 0x0})
/usr/local/go/src/database/sql/sql.go:3267 +0x8e
database/sql.(*Rows).Close(0x1e)
/usr/local/go/src/database/sql/sql.go:3263 +0x1d
panic({0x1505320, 0x19bfb00})
/usr/local/go/src/runtime/panic.go:1038 +0x215
database/sql.(*Rows).Next(0x0)
/usr/local/go/src/database/sql/sql.go:2944 +0x27
database/sql.(*Row).Scan(0xc0000afbd8, {0xc0000efb38, 0x11, 0x1})
/usr/local/go/src/database/sql/sql.go:3333 +0xb4
gorm.io/gorm/migrator.Migrator.CurrentDatabase({{0x0, 0xc000483350, {0x1659c58, 0xc00041a0f0}}})
/go/pkg/mod/gorm.io/gorm@v1.21.15/migrator/migrator.go:673 +0x8d
gorm.io/gorm/migrator.Migrator.HasTable.func1(0xc0000f8380)
/go/pkg/mod/gorm.io/gorm@v1.21.15/migrator/migrator.go:265 +0x51
gorm.io/gorm/migrator.Migrator.RunWithValue({{0x80, 0xc000483260, {0x1659c58, 0xc00041a0f0}}}, {0x1512320, 0xc0004fe2a0}, 0xc0000efcb8)
/go/pkg/mod/gorm.io/gorm@v1.21.15/migrator/migrator.go:50 +0x126
gorm.io/gorm/migrator.Migrator.HasTable({{0x0, 0xc000483260, {0x1659c58, 0xc00041a0f0}}}, {0x1512320, 0xc0004fe2a0})
/go/pkg/mod/gorm.io/gorm@v1.21.15/migrator/migrator.go:264 +0xe8
gorm.io/gorm/migrator.Migrator.AutoMigrate({{0x0, 0xc000426f90, {0x1659c58, 0xc00041a0f0}}}, {0xc00040f690, 0x0, 0x0})
/go/pkg/mod/gorm.io/gorm@v1.21.15/migrator/migrator.go:92 +0x127
gorm.io/gorm.(*DB).AutoMigrate(0x151a800, {0xc00040f690, 0x1, 0x1})
/go/pkg/mod/gorm.io/gorm@v1.21.15/migrator.go:26 +0x43
project/src/database.Init(0xc00041c230)
/Projects/project/src/database/init.go:12 +0x7b
project/src/database.TestInitMigratesDB(0x0)
/Projects/project/src/database/init_test.go:12 +0x5a
testing.tRunner(0xc0003a21a0, 0x15b5328)
/usr/local/go/src/testing/testing.go:1259 +0x102
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:1306 +0x35a
FAIL project/src/database 0.276s
FAIL
想通了。这是一个配置选项:&gorm.Config{ PrepareStmt: true }
。虽然这在生产中有效,但它不适用于 sqlmock。通过将其更改为修复它:&gorm.Config{}
.