Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

gorm.DB mocking for Unit Testing #1525

Closed
smikulcik opened this issue Jul 6, 2017 · 5 comments

Comments

@smikulcik
Copy link

commented Jul 6, 2017

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
@mykytanikitenko

This comment has been minimized.

Copy link

commented Jul 9, 2017

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.

@imiskolee

This comment has been minimized.

Copy link

commented Sep 1, 2017

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

@jirfag

This comment has been minimized.

Copy link

commented Sep 9, 2017

@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.

@jinzhu

This comment has been minimized.

Copy link
Owner

commented Feb 12, 2018

Refer #1424

@jinzhu jinzhu closed this Feb 12, 2018

@januszm

This comment has been minimized.

Copy link

commented Mar 26, 2018

👍 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)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
6 participants
You can’t perform that action at this time.