简体   繁体   中英

Golang GORM DB mock

I have to mock test a service.to create new service i need to pass gorm.DB{} but every time i pass it and run the test i get nil pointer error(panic). please help on how to mock gorm.DB{} instance correctly for unit testing.

func NewService(db *gorm.DB) Service {
    return &service{
        repo:  newReactionRepo(db),
    }
}

making the mock call in the test like this:-

mockDB = &gorm.DB{}

package.NewService(mockDB)

getting this error

testing.tRunner.func1.2({0x1648e40, 0x21cdd60})
    C:/Program Files/Go/src/testing/testing.go:1396 +0x24e
testing.tRunner.func1()
    C:/Program Files/Go/src/testing/testing.go:1399 +0x39f
panic({0x1648e40, 0x21cdd60})
    C:/Program Files/Go/src/runtime/panic.go:884 +0x212
gorm.io/gorm.(*DB).Session(0x21ef260, 0xc000861a50)
    C:/Users/acb/sdk/go1.17/pkg/mod/gorm.io/gorm@v1.24.2/gorm.go:215 +0x3b
gorm.io/gorm.(*DB).WithContext(...)

You could initialize db, ie:

//...
 mockDB := initDb()
 package.NewService(mockDB)
//...

func initDb() *gorm.DB {
    dsn := "host=localhost user=myuser password=mypassword dbname=mydb port=5432 sslmode=disable"

    db, err := gorm.Open(postgres.Open(dsn))
    if err != nil {
        log.Fatal("couldn't connect to db")
    }
    return db
}

Since the gorm.DB type is a struct, it makes unit testing with it directly a bit difficult. With the nil pointer error you are getting, you may need to modify your code to check for a nil value passed in so that it doesn't try and call methods on a nil pointer.

If you want to have unit tests, you could wrap your database operations in an interface, and then provide a mock implementation that does not use a real database. This can be done by creating an interface that defines the methods needed for interacting with the database, and then creating a struct that implements this interface for the actual database operations.

For example:

// Database is an interface that defines methods for interacting with a database.
type Database interface {
    Create(data interface{}) error
    GetByID(id int, result interface{}) error
}

// RealDatabase is a struct that implements the Database interface
// using a real GORM connection.
type RealDatabase struct {
    // db is a connection to the database
    db *gorm.DB
}

// Create saves the data in the database
func (rdb *RealDatabase) Create(data interface{}) error {
    return rdb.db.Create(data).Error
}

// GetByID retrieves the data by ID from the database
func (rdb *RealDatabase) GetByID(id int, result interface{}) error {
    return rdb.db.First(result, id).Error
}

// MockDatabase is a struct that implements the Database interface
// using a mock GORM connection.
type MockDatabase struct {
    // data is a map of ID to data used for mocking.
    data map[int]interface{}
}

// Create does not do anything but returns no errors
func (mdb *MockDatabase) Create(data interface{}) error {
    return nil
}

// GetByID returns the data for a given ID
func (mdb *MockDatabase) GetByID(id int, result interface{}) error {
    data, ok := mdb.data[id]
    if !ok {
        return fmt.Errorf("data not found for ID: %d", id)
    }
    result = data
    return nil
}

In this example, the RealDatabase struct uses GORM to interact with the database, while the MockDatabase struct uses a map to mimic the behavior of the real database. The Create method in the MockDatabase struct does nothing and returns no errors, and the GetByID method returns the data for a given ID. You can also add more functionality to the mock struct and the interface to mimic the behavior of the real database as needed.

Finally, you could also use the Gomock library , which provides tools to automatically generate mock versions of interfaces you provide to it.

You might try something using composition.

type DBInstance struct {
  *gorm.DB
}

Pass an instance of DBInstance to NewService(db DBInstance) instead of *gorm.DB . You can mock the methods of *gorm.DB that are used when unit testing your code. Example:

func (DBInstance) Find(dest interface{}, conds ...interface{}) (tx *DB) {
  // mock implementation of gorm.DB.Find()
}

When calling from actual code use initialize DB instance using the db instance:

dsn := "host=localhost user=myuser password=mypassword dbname=mydb port=5432 sslmode=disable"
db, err := gorm.Open(postgres.Open(dsn))
dbInstance := DBInstance{
  &db,
}

When calling from unit test, pass an empty uninitialized *gorm.DB object and mock all the required methods.

dbInstance := DBInstance{
  &gorm.DB{},
}

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