Problem writing a unit test using gorm and sql-mock - unit-testing

this is my funckion using package testing, gorm and sql-mock:
func Test_Create(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Errorf("Failed to open mock sql db, got error: %v", err)
}
if db == nil {
t.Error("db is null")
}
if mock == nil {
t.Error("mock is null")
}
defer db.Close()
pDb, err := gorm.Open(postgres.New(postgres.Config{
PreferSimpleProtocol: false,
DriverName: "postgres",
Conn: db,
}))
if err != nil {
t.Fatalf("gorm postgres fatal: %v", err)
}
student := Student{
ID: 12345,
Name: "Test user",
}
mock.ExpectBegin()
mock.ExpectExec(regexp.QuoteMeta(`INSERT INTO "students" ("id","name") VALUES ($1,$2) RETURNING "id"`)).WithArgs(student.ID, student.Name).WillReturnResult(sqlmock.NewResult(student.ID, 1))
mock.ExpectCommit()
err = pDb.Create(student).Error
if err != nil {
t.Error("error:", err)
}
}
I don't understand why I have an error creating a unit test as in the example below, can anyone help?

okay, I've already solved the problem. here's the working func:
func Test_Create(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Errorf("Failed to open mock sql db, got error: %v", err)
}
if db == nil {
t.Error("db is null")
}
if mock == nil {
t.Error("mock is null")
}
defer db.Close()
dialector := postgres.New(postgres.Config{
DSN: "sqlmock_db_0",
DriverName: "postgres",
Conn: db,
PreferSimpleProtocol: true,
})
pDb, err := gorm.Open(dialector, &gorm.Config{})
if err != nil {
t.Fatalf("gorm postgres fatal: %v", err)
}
student := Student{
ID: 12345,
Name: "Test user",
}
mock.ExpectBegin()
mock.ExpectQuery(
regexp.QuoteMeta(`INSERT INTO "students" ("name","id") VALUES ($1,$2) RETURNING "id"`)).
WithArgs(student.Name, student.ID).WillReturnRows(sqlmock.NewRows([]string{"id"}).AddRow(student.ID))
mock.ExpectCommit()
if err = pDb.Create(&student).Error; err != nil {
t.Errorf("Failed to insert to gorm db, got error: %v", err)
t.FailNow()
}
err = mock.ExpectationsWereMet()
if err != nil {
t.Errorf("Failed to meet expectations, got error: %v", err)
}
}

Related

Unit testing with gorm

I have found a lot of questions regarding gorm mocking related to the old V1 package: github.com/jinzhu/gorm. with the usage of github.com/DATA-DOG/go-sqlmock.
I didn't find much with the v2.
My simple question is:
Suppose I have this storage package code:
...
type Storage struct {
GormDB *gorm.DB
SqlDB *sql.DB
mutex sync.Mutex
ReadTimeout int
WriteTimeout int
}
func (ps *Storage) Open(settings *Settings) error {
if err := settings.Validate(); err != nil {
return err
}
ps.mutex.Lock()
defer ps.mutex.Unlock()
if ps.GormDB != nil {
return nil
}
gormDB, err := gorm.Open(postgres.New(postgres.Config{
DSN: settings.GetDSN(),
}), &gorm.Config{
SkipDefaultTransaction: true,
})
if err != nil {
return fmt.Errorf("%s: %v", DBConnectError, err)
}
ps.GormDB = gormDB
sqlDB, err := ps.GormDB.DB()
if err != nil {
return fmt.Errorf("%s: %v", DBRetrievalError, err)
}
ps.SqlDB = sqlDB
ps.SqlDB.SetMaxIdleConns(settings.MaxIdleConnections)
ps.SqlDB.SetMaxOpenConns(settings.MaxOpenConnections)
ps.ReadTimeout = settings.ReadTimeout
ps.WriteTimeout = settings.WriteTimeout
return nil
}
How can I unit-test this function with a simple check that gorm.Open received the expected config?
I don't see any other way than passing the ORM interface to this method... It would be a tough solution to write a gorm interface and mock it myself...
Can anyone please provide a simple example of mocking such a function?
P.S.
I don't want to run the docker with Postgres for this test. It is a simple unit test, not integration.
EDIT:
Suppose I just want to mock the connection to make gorm.Open to not return an error. How can I do it?
sqlmock.NewWithDSN(settings.GetDSN()) does not help
Answering my own question here.
Thanks to #flimzy for pointing me in the right direction.
To be able to test the storage open need to modify the function:
func (ps *Storage) Open(settings *Settings, postgresConfig *postgres.Config) error {
if err := settings.Validate(); err != nil {
return err
}
ps.mutex.Lock()
defer ps.mutex.Unlock()
if ps.GormDB != nil {
return nil
}
gormDB, err := gorm.Open(postgres.New(*postgresConfig), &gorm.Config{
SkipDefaultTransaction: true,
})
if err != nil {
return fmt.Errorf("%s: %v", DBConnectError, err)
}
ps.GormDB = gormDB
sqlDB, err := ps.GormDB.DB()
if err != nil {
return fmt.Errorf("%s: %v", DBRetrievalError, err)
}
ps.SqlDB = sqlDB
ps.SqlDB.SetMaxIdleConns(settings.MaxIdleConnections)
ps.SqlDB.SetMaxOpenConns(settings.MaxOpenConnections)
ps.ReadTimeout = settings.ReadTimeout
ps.WriteTimeout = settings.WriteTimeout
return nil
}
To mock gorm connection we just pass go-sqlmock connection in the postgres config of the form postgres driver:
package postgres
import (
"github.com/DATA-DOG/go-sqlmock"
. "github.com/onsi/ginkgo"
. "github.com/onsi/gomega"
)
var _ = Describe("Postgres Storage", func() {
...
Describe("Open", func() {
Context("when settings are valid", func() {
It("should open a DB connection and initialize storage", func() {
sqlDB, sqlMock, _ := sqlmock.New()
sqlMock.ExpectPing()
err := storage.Open(settings, postgres.Config{Conn: sqlDB})
dbStats := storage.sqlDB.Stats()
Expect(err).ShouldNot(HaveOccurred())
Expect(dbStats.MaxOpenConnections).To(Equal(settings.MaxOpenConnections))
Expect(dbStats.OpenConnections).To(Equal(1))
Expect(storage.ReadTimeout).To(Equal(settings.ReadTimeout))
Expect(storage.WriteTimeout).To(Equal(settings.WriteTimeout))
Expect(sqlMock.ExpectationsWereMet()).To(BeNil())
})
})
})
})

Go GORM Mocking Begin Expected

I am trying to test a function that gets some details then update details in db. I am using gorm for my ORM and mocking db exec with DATA-DOG/sqlmock. But I keep getting expected begin error but not sure where I am messing up. I tried many variations of the code where I remove the expectBegin expectCommit etc.
Here is the code I am trying to test:
// UpsertUserProfile saves the user if doesn't exists and adds the OAuth profile
// and updates existing user info if record exist in db
func (o *ORM) UpsertUserProfile(gu *goth.User) (*models.User, error) {
db := o.DB
up := &models.UserProfile{}
u, err := models.GothUserToDBUser(gu, false)
if err != nil {
return nil, err
}
tx := db.Where("email = ?", gu.Email).First(u)
if tx.Error != nil && tx.Error != gorm.ErrRecordNotFound {
return nil, tx.Error
}
if tx := db.Model(u).Save(u); tx.Error != nil {
return nil, tx.Error
}
tx = db.Where("email = ? AND provider = ? AND external_user_id = ?", gu.Email, gu.Provider, gu.UserID).First(up)
if tx.Error != nil && tx.Error != gorm.ErrRecordNotFound {
return nil, tx.Error
}
up, err = models.GothUserToDBUserProfile(gu, false)
if err != nil {
return nil, err
}
up.User = *u
if tx := db.Model(up).Save(up); tx.Error != nil {
return nil, tx.Error
}
return u, nil
}
And here is the test code
func TestORM_UpsertUserProfile(t *testing.T) {
mockdb, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
gormDB, err := gorm.Open(postgres.New(postgres.Config{
Conn: mockdb,
}), &gorm.Config{})
if err != nil {
t.Fatal(err.Error())
}
defer mockdb.Close()
id, _ := uuid.NewV4()
rows := sqlmock.NewRows([]string{"id", "first_name", "last_name"}).
AddRow(id.String(), "fname", "lname")
profileRows := sqlmock.NewRows([]string{"email"}).AddRow("email")
// Test cases
t.Run("success", func(t *testing.T) {
o := &ORM{
DB: gormDB,
}
now := time.Now()
fName := "fname"
lName := "lname"
wantUsr := models.User{
Email: "email",
FirstName: &fName,
LastName: &lName,
BaseModelSoftDelete: models.BaseModelSoftDelete{
BaseModel: models.BaseModel{
ID: id,
CreatedAt: &now,
UpdatedAt: &now,
},
},
}
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "users"`)).WithArgs("email").WillReturnRows(rows)
mock.ExpectBegin()
mock.ExpectExec(regexp.QuoteMeta(`UPDATE`)).WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
mock.ExpectBegin()
mock.ExpectQuery(regexp.QuoteMeta(`SELECT * FROM "user_profiles"`)).WithArgs("email", "provider", "testID").WillReturnRows(profileRows)
mock.ExpectCommit()
mock.ExpectBegin()
got, err := o.UpsertUserProfile(&goth.User{Email: "email", UserID: "testID", Provider: "provider"})
if (err != nil) != false {
t.Errorf("ORM.UpsertUserProfile() error = %v, wantErr %v", err, false)
return
}
if !reflect.DeepEqual(got, wantUsr) {
t.Errorf("ORM.UpsertUserProfile() = %v, want %v", got, wantUsr)
}
})
}
but the test keeps returning this error
SELECT * FROM "user_profiles" WHERE email = 'email' AND provider = 'provider' AND external_user_id = 'testID' ORDER BY "user_profiles"."id" LIMIT 1
/Users/public/Projects/go-rest-service/internal/orm/orm_test.go:260: ORM.UpsertUserProfile() error = call to Query 'SELECT * FROM "user_profiles" WHERE email = $1 AND provider = $2 AND external_user_id = $3 ORDER BY "user_profiles"."id" LIMIT 1' with args [{Name: Ordinal:1 Value:email} {Name: Ordinal:2 Value:provider} {Name: Ordinal:3 Value:testID}], was not expected, next expectation is: ExpectedBegin => expecting database transaction Begin, wantErr false

Getting unexpected error while executing queries for unit testing with sqlmock

I have the following function that takes a slice of type SchoolDbEntry and bulk inserts it into a DB.
type SchoolRegistry struct {
Conn *sqlx.DB
}
insertStudentEntry = `insert into StudentEntries (registerId, studentId, fee, admissionDate) values (:registerId, :studentId, :fee, :admissionDate)`
func (s *SchoolRegistry) AddStudentEntries(ctx context.Context, entry []StudentDbEntry) error {
requestHeaders := helper.FetchHeaders(ctx)
query, args, err := sqlx.Named(insertStudentEntry, entry)
if err != nil {
return exception.DBInternalError
}
_, err = s.Conn.Exec(query, args...)
if err != nil {
return cutomModels.NewError(err.Error()
}
return nil
}
I am trying to test this using sqlmock using the following functions
var repo *SchoolRegistry
func TestAddStudentEntriesSuccess(t *testing.T) {
db, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was not expected when opening a stub database connection", err)
}
sqlxDb := sqlx.NewDb(db, "sqlmock")
defer sqlxDb.Close()
repo = &SchoolRegistry{sqlxDb}
if err := logger.Init(logLevel, logTimeFormat); err != nil {
fmt.Printf("failed to initialize logger: %v", err)
}
query := `insert into StudentEntries (registerId, studentId, fee, admissionDate) values (:registerId, :studentId, :fee, :admissionDate)`
args := []models.StudentDbEntry{
{
RegisterID: 1,
StudentID: 1,
Fee: "100",
AdmissionDate: "10-20-2015",
},
}
prep := mock.ExpectPrepare(query)
prep.ExpectExec().WithArgs(1,1,"100","10-20-2015").WillReturnResult(sqlmock.NewResult(0, 0))
err := repo.AddStudentEntries(helper.FetchTestContext(), args)
assert.NoError(t, err)
}
However, the tests are failing. I am getting
call to ExecQuery 'insert into StudentEntries (registerId, studentId, fee, admissionDate) values (?, ?, ?, ?)' with args [{Name: Ordinal:1 Value:1} {Name: Ordinal:2 Value:1} {Name: Ordinal:3 Value:"100"} {Name: Ordinal:4 Value:"10-20-2015"}], was not expected, next expectation is: ExpectedPrepare => expecting Prepare statement which:
- matches sql: 'insert into StudentEntries (registerId, studentId, fee, admissionDate) values (:registerId, :studentId, :fee, :admissionDate)'
error when no errors are expected ( that is, after this line : _, err = s.Conn.Exec(query, args...) ). The function works fine when I tested it as a part of the actual code. The issue is just with the unit testing.

Golang Unit Testing Erorr when test function with query db

i start learning unit test on golang, i have do a simple test than it work, but try on real case, i found some errors, this is my test code:
test code
func TestGetAllCompetency(t *testing.T) {
_, err := competencyService.GetAllCompetency()
if err != nil {
t.Error("failed to get competency")
}
}
GetAllCompetency Code
func GetAllCompetency() ([]models.Competency, error) {
data := []models.Competency{}
res := database.Conn.
Table("competency").Order("name").
Where("competency.is_delete = ?", false).
Find(&data)
err := res.Error
if err != nil {
log.Println("[Error] competencyService.GetAllData : ", err)
return []models.Competency{}, err
}
return data, nil
}

Go sqlmock SELECT missing expected query

Go here. Trying to figure out how to use SQL mock v2.
Here's my interface:
type OrderPersister interface {
FetchOrderById(string) (*Order, error)
}
And my implementation of that interface:
type DbPersister struct {
Config config.DbConfig
GormDB *gorm.DB
}
func (op DbPersister) FetchOrderById(orderId string) (*Order, error) {
Order := &Order{}
orderUuid, err := uuid.Parse(orderId)
if err != nil {
return nil, err
}
if err := op.GormDB.Table("orders").
Select(`orders.order_id,
orders.user_id,
orders.quantity,
orders.status
addresses.line_1,
users.email`).
Joins("join addresses on addresses.address_id = orders.address_id").
Joins("join users on users.user_id = orders.user_id").
Where("orders.order_id = ?", orderUuid).
First(Order).Error; err != nil {
return nil, err
}
return Order, nil
}
And my unit test (including setup/init):
import (
"database/sql"
"testing"
"github.com/google/uuid"
"github.com/jinzhu/gorm"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"gopkg.in/DATA-DOG/go-sqlmock.v2"
)
type Suite struct {
suite.Suite
DB *gorm.DB
mock sqlmock.Sqlmock
dbPersister OrderPersister
}
func (s *Suite) SetupSuite() {
var (
db *sql.DB
err error
)
db, s.mock, err = sqlmock.New()
require.NoError(s.T(), err)
s.DB, err = gorm.Open("postgres", db)
require.NoError(
s.T(), err)
s.DB.LogMode(true)
s.dbPersister = DbPersister{
Config: config.DbConfig{
DbHost: "",
DbPort: "",
DbName: "",
DbUsername: "",
DbPassword: "",
},
GormDB: s.DB,
}
}
func (s *Suite) BeforeTest(_, _ string) {
var (
db *sql.DB
err error
)
db, s.mock, err = sqlmock.New()
require.NoError(s.T(), err)
s.DB, err = gorm.Open("postgres", db)
require.NoError(s.T(), err)
s.DB.LogMode(true)
}
func (s *Suite) AfterTest(_, _ string) {
require.NoError(s.T(), s.mock.ExpectationsWereMet())
}
func TestInit(t *testing.T) {
suite.Run(t, new(Suite))
}
func (s *Suite) TestFetchOrderById() {
// given
orderId := uuid.New()
quantity := 1
status := "ready"
line1 := "201"
email := "jsmith#example.com"
// s.mock.ExpectBegin()
s.mock.ExpectQuery(`SELECT`).
WillReturnRows(sqlmock.NewRows([]string{"orders.order_id","orders.user_id","orders.quantity","orders.status",
"addresses.line_1","user_logins.email"}).
AddRow(sqlmock.AnyArg(), sqlmock.AnyArg(), quantity, status, totalExclTax, shippingExclTax,
totalTaxAmt, line1, state, zip, locality, upc, email, firstName, lastName))
_, err := s.dbPersister.FetchOrderById(orderId.String())
s.mock.ExpectCommit()
require.NoError(s.T(), err)
}
When this runs the test fails for the following reason:
--- FAIL: TestInit (0.00s)
--- FAIL: TestInit/TestFetchOrderById (0.00s)
db_test.go:67:
Error Trace: db_test.go:67
suite.go:137
panic.go:969
rows.go:134
db_test.go:99
Error: Received unexpected error:
there is a remaining expectation which was not matched: ExpectedQuery => expecting Query, QueryContext or QueryRow which:
- matches sql: 'SELECT'
- is without arguments
Test: TestInit/TestFetchOrderById
All I'm trying to do is confirm that the GormDB instance was queried with the SELECT statement specified in the FetchOrderById function.
Does anybody know what I need to do to achieve this and get the test to pass?
I decided to Go (no pun intended) with Java instead.