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", ...(ormock) 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.DBis 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", ...(ormock) 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.DBis used/initialized like so: ```go package modelsvar 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{})