简体   繁体   中英

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

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

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{} .

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM