I am using go-sqlmock
for the first time and I am trying to write a test for post operation. I am using gorm
and gin
.
s.mock.ExpectQuery(regexp.QuoteMeta(....
I am not what is the issue here. I have posted both the test and the output.code
will be as it is randomly generated in the api controller. Is there a way to assign a generic number in the code
field.The test file
package unit
import (
"net/http"
"net/http/httptest"
"regexp"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/SamiAlsubhi/go/controllers"
"github.com/SamiAlsubhi/go/routes"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type Suite struct {
suite.Suite
DB *gorm.DB
mock sqlmock.Sqlmock
router *gin.Engine
}
func (s *Suite) SetupSuite(t *testing.T) {
conn, mock, err := sqlmock.New()
if err != nil || conn == nil {
t.Errorf("Failed to open mock sql db, got error: %v", err)
}
s.mock = mock
dialector := postgres.New(postgres.Config{
DSN: "sqlmock_db_0",
DriverName: "postgres",
Conn: conn,
PreferSimpleProtocol: true,
})
if db, err := gorm.Open(dialector, &gorm.Config{}); err != nil || db == nil {
t.Errorf("Failed to open gorm v2 db, got error: %v", err)
} else {
s.DB = db
}
api := &controllers.API{Db: s.DB}
s.router = routes.SetupRouter(api)
}
func TestSetup(t *testing.T) {
suite.Run(t, new(Suite))
}
func (s *Suite) AfterTest(_, _ string) {
require.NoError(s.T(), s.mock.ExpectationsWereMet())
}
func (s *Suite) Test_GetOTP() {
var (
phone = "99999999"
code = "123456"
)
s.mock.ExpectQuery(regexp.QuoteMeta(
`INSERT INTO "otps" ("phone","code") VALUES ($1,$2) RETURNING "otps"."id"`)).
WithArgs(phone, code).
WillReturnRows(sqlmock.NewRows([]string{"id"}).
AddRow(1))
s.mock.ExpectCommit()
w := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/api/auth/get-otp/"+phone, nil)
require.NoError(s.T(), err)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), 200, w.Code)
//require.Nil(s.T(), deep.Equal(&model.Person{ID: id, Name: name}, w.Body))
}
the output.
--- FAIL: TestSetup (0.00s)
--- FAIL: TestSetup/Test_GetOTP (0.00s)
/Users/sami/Desktop/SamiAlsubhi/go/test/unit/suite.go:63: test panicked: runtime error: invalid memory address or nil pointer dereference
goroutine 26 [running]:
runtime/debug.Stack()
/usr/local/go/src/runtime/debug/stack.go:24 +0x65
github.com/stretchr/testify/suite.failOnPanic(0xc000001a00)
/Users/sami/Desktop/golang/pkg/mod/github.com/stretchr/[email protected]/suite/suite.go:63 +0x3e
panic({0x49e96a0, 0x5193810})
/usr/local/go/src/runtime/panic.go:1038 +0x215
github.com/SamiAlsubhi/go/test/unit.(*Suite).AfterTest(0x4abe61b, {0x4becfd0, 0xc000468940}, {0x0, 0x0})
/Users/sami/Desktop/SamiAlsubhi/go/test/unit/setup_test.go:60 +0x1c
github.com/stretchr/testify/suite.Run.func1.1()
/Users/sami/Desktop/golang/pkg/mod/github.com/stretchr/[email protected]/suite/suite.go:137 +0x1b7
panic({0x49e96a0, 0x5193810})
/usr/local/go/src/runtime/panic.go:1038 +0x215
github.com/SamiAlsubhi/go/test/unit.(*Suite).Test_GetOTP(0xc000468940)
/Users/sami/Desktop/SamiAlsubhi/go/test/unit/setup_test.go:69 +0x4f
reflect.Value.call({0xc000049140, 0xc000010308, 0x13}, {0x4abf50c, 0x4}, {0xc000080e70, 0x1, 0x1})
/usr/local/go/src/reflect/value.go:543 +0x814
reflect.Value.Call({0xc000049140, 0xc000010308, 0xc000468940}, {0xc0003c9e70, 0x1, 0x1})
/usr/local/go/src/reflect/value.go:339 +0xc5
github.com/stretchr/testify/suite.Run.func1(0xc000001a00)
/Users/sami/Desktop/golang/pkg/mod/github.com/stretchr/[email protected]/suite/suite.go:158 +0x4b6
testing.tRunner(0xc000001a00, 0xc000162000)
/usr/local/go/src/testing/testing.go:1259 +0x102
created by testing.(*T).Run
/usr/local/go/src/testing/testing.go:1306 +0x35a
FAIL
coverage: [no statements]
FAIL github.com/SamiAlsubhi/go/test/unit 0.912s
FAIL
Solution to the first issue:
when using testify/suite
, There are bunch of methods if created for the Suite
struct, they will be automatically executed when running the test. That being said, These methods will pass through an interface filter. In the case of .SetupSuite
, it has to have NO arguments and No return, in order to run.
Solution to the second issue:
There is a way in go-sqlmock
to match any kind of data by using sqlmock.AnyArg()
.
Fixed code:
package unit
import (
"encoding/json"
"fmt"
"math/rand"
"net/http"
"net/http/httptest"
"regexp"
"testing"
"github.com/DATA-DOG/go-sqlmock"
"github.com/SamiAlsubhi/go/controllers"
"github.com/SamiAlsubhi/go/routes"
"github.com/gin-gonic/gin"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
"github.com/stretchr/testify/suite"
"gorm.io/driver/postgres"
"gorm.io/gorm"
)
type Suite struct {
suite.Suite
DB *gorm.DB
mock sqlmock.Sqlmock
router *gin.Engine
}
func (s *Suite) SetupSuite() {
//t.Logf("setup start")
conn, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherRegexp))
if err != nil || conn == nil {
panic(fmt.Sprintf("Failed to open mock sql db, got error: %v", err))
}
s.mock = mock
dialector := postgres.New(postgres.Config{
DSN: "sqlmock_db_0",
DriverName: "postgres",
Conn: conn,
PreferSimpleProtocol: true,
})
if db, err := gorm.Open(dialector, &gorm.Config{SkipDefaultTransaction: true}); err != nil || db == nil {
panic(fmt.Sprintf("Failed to open gorm v2 db, got error: %v", err))
} else {
s.DB = db
}
api := &controllers.API{Db: s.DB, IsTesting: true}
s.router = routes.SetupRouter(api)
}
func TestSetup(t *testing.T) {
suite.Run(t, new(Suite))
}
// func (s *Suite) AfterTest(_, _ string) {
// require.NoError(s.T(), s.mock.ExpectationsWereMet())
// }
func (s *Suite) Test_GetOTP_Non_Existing_Phone() {
/*
This to test getting OTP for a phone number that does not exist in the otps table
*/
phone := fmt.Sprintf("%v", 90000000+rand.Intn(99999999-90000000))
s.mock.MatchExpectationsInOrder(false)
s.mock.ExpectQuery(regexp.QuoteMeta(`SELECT count(*) FROM "otps" WHERE phone = $1 AND "otps"."deleted_at" IS NULL`)).
WithArgs(phone).
WillReturnRows(sqlmock.NewRows([]string{"count"}).
AddRow(0))
s.mock.ExpectQuery(regexp.QuoteMeta(
`INSERT INTO "otps" ("created_at","updated_at","deleted_at","phone","code") VALUES ($1,$2,$3,$4,$5) RETURNING "id"`)).
WithArgs(sqlmock.AnyArg(), sqlmock.AnyArg(), sqlmock.AnyArg(), phone, sqlmock.AnyArg()).
WillReturnRows(sqlmock.NewRows([]string{"id"}).
AddRow(1))
w := httptest.NewRecorder()
req, err := http.NewRequest("GET", "/api/auth/get-otp/"+phone, nil)
require.NoError(s.T(), err)
s.router.ServeHTTP(w, req)
assert.Equal(s.T(), 200, w.Code)
//parse response
var response gin.H
err = json.Unmarshal(w.Body.Bytes(), &response)
require.NoError(s.T(), err)
_, ok := response["expiry_in"]
assert.True(s.T(), ok)
require.NoError(s.T(), s.mock.ExpectationsWereMet())
}