英文:
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) {
// 获取 db 和 mock
sqlDB, mock, err := sqlmock.New(
sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp),
)
if err != nil {
log.Fatalf("[sqlmock new] %s", err)
}
defer sqlDB.Close()
// 创建 dialector
dialector := mysql.New(mysql.Config{
Conn: sqlDB,
DriverName: "mysql",
})
// 当 gorm 打开数据库时,将运行 SELECT VERSION() 查询,因此我们需要在这里进行预期
columns := []string{"version"}
mock.ExpectQuery("SELECT VERSION()").WithArgs().WillReturnRows(
mock.NewRows(columns).FromCSVString("1"),
)
// 打开数据库
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 自动迁移数据库。
func Init(db *gorm.DB) {
// 迁移模式
// 这里会引发 panic:
// panic: runtime error: invalid memory address or nil pointer dereference
// User 在这里被定义和实例化
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()
// 在这里失败
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
英文:
Summary
I'm trying to use go-sqlmock
with gorm
for testing. I want to write a test for the initial database migration, but I've hit a panic: runtime error: invalid memory address or nil pointer dereference
and I've been having trouble figuring out why. Judging by the error stack, I think it's this statement that does it: db.AutoMigrate(&models.User{})
. I'm not sure why, as db
has allegedly started up successfully by this point and models.User
is defined and instantiated as an argument to db.AutoMigrate
. I have a feeling the error is in the mocks.NewDatabase
function, but I'm at a loss.
Not sure if anyone has the time or will to take a peak at the relevant code and help me out? I've noted in the code where the failures occur (they're in the final two blocks of code). Let me know if any additional context would help.
Relevant Code
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{})
}
Now the test:
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)
}
And the log
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
答案1
得分: 1
搞定了。问题出在配置选项上:&gorm.Config{ PrepareStmt: true }
。虽然在生产环境中可以工作,但在sqlmock中无法正常工作。通过将其更改为&gorm.Config{}
来解决了这个问题。
英文:
Figured it out. It was a config option: &gorm.Config{ PrepareStmt: true }
. While this works in production, it does not work with sqlmock. Fixed it by changing it to: &gorm.Config{}
.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论