使用go-sqlmock创建gorm数据库(运行时错误)

huangapple go评论76阅读模式
英文:

Creating a gorm database with go-sqlmock (runtime error)

问题

摘要

我正在尝试在测试中使用go-sqlmockgorm。我想为初始数据库迁移编写一个测试,但是我遇到了一个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{}.

huangapple
  • 本文由 发表于 2021年10月14日 12:30:05
  • 转载请务必保留本文链接:https://go.coder-hub.com/69565095.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定