I would like to be able to use github.com/stretchr/testify/mock for unit testing my gorm DB instance. However, gorm.DB is a struct and its methods return pointers to the struct. For testify/mock, we need methods to work on interfaces. For instance, gorm.DB.Find must return an interface, not a struct pointer for mocking to work.

What version of Go are you using (go version)?

go version go1.8.3 darwin/amd64

Which database and its version are you using?

sqlite 3.16.0 2016-11-04 19:09:39 0e5ffd9123d6d2d2b8f3701e8a73cc98a3a7ff5f

What did you do?

Here I have a simple database program that has one model, Item, and one database dependent method. I'd like to unit test GetItems(), but I need to mock out the database instance to do that.

main.go

package main

import (
    "github.com/jinzhu/gorm"
    _ "github.com/jinzhu/gorm/dialects/sqlite"
)

type Item struct {
    gorm.Model
    Name string
}

func setupDB() *gorm.DB {
    db, err := gorm.Open("sqlite3", "test.db")
    if err != nil {
        panic("failed to connect database")
    }
    db.AutoMigrate(&Item{})

    return db
}

// GetItems Function that I want to test
func GetItems(db IDatabase) []*Item {
    var items []*Item
    db.Find(&items)
    return items
}

// IDatabase database interface for mocking
type IDatabase interface {
    Find(out interface{}, where ...interface{}) IDatabase
}

func main() {
    db := setupDB()
    defer db.Close()
    GetItems(db)
}

main_test.go

package main

import (
    "testing"

    "github.com/stretchr/testify/mock"
)

// MockDatabase mock database for testing
type MockDatabase struct {
    mock.Mock
}

// Find find
func (m *MockDatabase) Find(out interface{}, where ...interface{}) IDatabase {
    args := m.Called(out, where)
    return args.Get(0).(IDatabase)
}

// TestGetItems test function
func TestGetItems(t *testing.T) {
    m := &MockDatabase{}

    m.On("Find", mock.Anything, mock.Anything).Return(m)

    GetItems(m)

    m.AssertExpectations(t)
}

The tests run fine and pass, but when I try to run the main file, it fails.

$ go run main.go 
# command-line-arguments
./main.go:38: cannot use db (type *gorm.DB) as type IDatabase in argument to GetItems:
    *gorm.DB does not implement IDatabase (wrong type for Find method)
        have Find(interface {}, ...interface {}) *gorm.DB
        want Find(interface {}, ...interface {}) IDatabase

Comment From: mykytanikitenko

Because go's type system is not covariant\contrvariant. Find(interface {}, ...interface {}) gorm.DB is not Find(interface {}, ...interface {}) IDatabase, even if gorm.DB implemented IDatabase interface.

Comment From: imiskolee

i will find a tdd solution for gorm now. and i think we need a mock sql.Database implemention ?

Comment From: jirfag

@smikulcik @mykytanikitenko @imiskolee @sunfmin @levinalex I use sqlmock to mock-test GORM queries. Example is here, search e.g. testUserSelectAll. First I tried go-testdb and custom gorm.DB wrapper to interface, but current solution with sqlmock is the simplest.

Comment From: jinzhu

Refer https://github.com/jinzhu/gorm/pull/1424

Comment From: januszm

👍 for adding a db mock for use in (Unit) tests. Would something like gorm.Open("test", ... (or mock) make any sense?

Anyway, currently, as others stated, mocks can be implemented with https://github.com/DATA-DOG/go-sqlmock like:

import (
  "github.com/jinzhu/gorm"
  "gopkg.in/DATA-DOG/go-sqlmock.v1"
)
...
func TestMyGoodness(t *testing.T) {
  db, mock, _ := sqlmock.New()
  models.Db, _ = gorm.Open("postgres", db)
  sqlRows := sqlmock.NewRows([]string{"details"}).
    AddRow(`{"name": "foo", "type": "bar", ... }`
  mock.ExpectQuery("^SELECT (.+) FROM \"products\" (.+)$").WillReturnRows(sqlRows)
...
// some http request recording or other operations
// and then the usual expected := , if ... != t.Errorf combo:
expected := `{"products":[{"details":{"name": "foo", "type": "bar"}}]}`

The above assumes that *gorm.DB is used/initialized like so:

package models

var Db *gorm.DB

...
// somewhere else:
scope := models.Db.Select("id, details, created_at, updated_at")
...
scope.Find(&products)

Comment From: kishankishore29

👍 for adding a db mock for use in (Unit) tests. Would something like gorm.Open("test", ... (or mock) make any sense?

Anyway, currently, as others stated, mocks can be implemented with https://github.com/DATA-DOG/go-sqlmock like:

go import ( "github.com/jinzhu/gorm" "gopkg.in/DATA-DOG/go-sqlmock.v1" ) ... func TestMyGoodness(t *testing.T) { db, mock, _ := sqlmock.New() models.Db, _ = gorm.Open("postgres", db) sqlRows := sqlmock.NewRows([]string{"details"}). AddRow(`{"name": "foo", "type": "bar", ... }` mock.ExpectQuery("^SELECT (.+) FROM \"products\" (.+)$").WillReturnRows(sqlRows) ... // some http request recording or other operations // and then the usual expected := , if ... != t.Errorf combo: expected := `{"products":[{"details":{"name": "foo", "type": "bar"}}]}`

The above assumes that *gorm.DB is used/initialized like so:

```go package models

var Db *gorm.DB

... // somewhere else: scope := models.Db.Select("id, details, created_at, updated_at") ... scope.Find(&products) ```

Is there an update on the gorm.Open("test", ... method ?

Comment From: 1saifj

👍 for adding a db mock for use in (Unit) tests. Would something like gorm.Open("test", ... (or mock) make any sense? Anyway, currently, as others stated, mocks can be implemented with https://github.com/DATA-DOG/go-sqlmock like: go import ( "github.com/jinzhu/gorm" "gopkg.in/DATA-DOG/go-sqlmock.v1" ) ... func TestMyGoodness(t *testing.T) { db, mock, _ := sqlmock.New() models.Db, _ = gorm.Open("postgres", db) sqlRows := sqlmock.NewRows([]string{"details"}). AddRow(`{"name": "foo", "type": "bar", ... }` mock.ExpectQuery("^SELECT (.+) FROM \"products\" (.+)$").WillReturnRows(sqlRows) ... // some http request recording or other operations // and then the usual expected := , if ... != t.Errorf combo: expected := `{"products":[{"details":{"name": "foo", "type": "bar"}}]}`

The above assumes that *gorm.DB is used/initialized like so: ```go package models

var Db *gorm.DB

... // somewhere else: scope := models.Db.Select("id, details, created_at, updated_at") ... scope.Find(&products) ```

Is there an update on the gorm.Open("test", ... method ?

you can use it like this:

        sqlDB, mockDB, err = sqlmock.New()
        db, err = gorm.Open(postgres.New(postgres.Config{
            Conn: sqlDB,
        }), &gorm.Config{})