Support
Quality
Security
License
Reuse
Coming Soon for all Libraries!
Currently covering the most popular Java, JavaScript and Python libraries. See a SAMPLE HERE.
kandi's functional review helps you automatically verify the functionalities of the libraries and avoid rework.
Sql mock driver for golang to test database interactions
Install
go get github.com/DATA-DOG/go-sqlmock
Something you may want to test, assuming you use the
package main
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
func recordStats(db *sql.DB, userID, productID int64) (err error) {
tx, err := db.Begin()
if err != nil {
return
}
defer func() {
switch err {
case nil:
err = tx.Commit()
default:
tx.Rollback()
}
}()
if _, err = tx.Exec("UPDATE products SET views = views + 1"); err != nil {
return
}
if _, err = tx.Exec("INSERT INTO product_viewers (user_id, product_id) VALUES (?, ?)", userID, productID); err != nil {
return
}
return
}
func main() {
// @NOTE: the real connection is not required for tests
db, err := sql.Open("mysql", "root@/blog")
if err != nil {
panic(err)
}
defer db.Close()
if err = recordStats(db, 1 /*some user id*/, 5 /*some product id*/); err != nil {
panic(err)
}
}
Tests with sqlmock
package main
import (
"fmt"
"testing"
"github.com/DATA-DOG/go-sqlmock"
)
// a successful case
func TestShouldUpdateStats(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)
}
defer db.Close()
mock.ExpectBegin()
mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectExec("INSERT INTO product_viewers").WithArgs(2, 3).WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
// now we execute our method
if err = recordStats(db, 2, 3); err != nil {
t.Errorf("error was not expected while updating stats: %s", err)
}
// we make sure that all expectations were met
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
}
// a failing test case
func TestShouldRollbackStatUpdatesOnFailure(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)
}
defer db.Close()
mock.ExpectBegin()
mock.ExpectExec("UPDATE products").WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectExec("INSERT INTO product_viewers").
WithArgs(2, 3).
WillReturnError(fmt.Errorf("some error"))
mock.ExpectRollback()
// now we execute our method
if err = recordStats(db, 2, 3); err == nil {
t.Errorf("was expecting an error, but there was none")
}
// we make sure that all expectations were met
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
}
Customize SQL query matching
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
Matching arguments like time.Time
type AnyTime struct{}
// Match satisfies sqlmock.Argument interface
func (a AnyTime) Match(v driver.Value) bool {
_, ok := v.(time.Time)
return ok
}
func TestAnyTimeArgument(t *testing.T) {
t.Parallel()
db, mock, err := New()
if err != nil {
t.Errorf("an error '%s' was not expected when opening a stub database connection", err)
}
defer db.Close()
mock.ExpectExec("INSERT INTO users").
WithArgs("john", AnyTime{}).
WillReturnResult(NewResult(1, 1))
_, err = db.Exec("INSERT INTO users(name, created_at) VALUES (?, ?)", "john", time.Now())
if err != nil {
t.Errorf("error '%s' was not expected, while inserting a row", err)
}
if err := mock.ExpectationsWereMet(); err != nil {
t.Errorf("there were unfulfilled expectations: %s", err)
}
}
Run tests
go test -race
Issue with go-sqlmock testing in the expected query part
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())
}
`could not match actual sql` error while mocking gorm `updates` using go-sqlmock?
db, mock, err := sqlmock.New(sqlmock.QueryMatcherOption(sqlmock.QueryMatcherEqual))
How to mock gorm insert with go-sql (postgres)
mock.ExpectBegin()
mock.ExpectExec(regexp.QuoteMeta("INSERT INTO \"tests\" (\"first_name\") VALUES (?)")).WithArgs("c").WillReturnResult(sqlmock.NewResult(1, 1))
mock.ExpectCommit()
QUESTION
Issue with go-sqlmock testing in the expected query part
Asked 2022-Jan-24 at 08:37I 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/testify@v1.7.0/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/testify@v1.7.0/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/testify@v1.7.0/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
ANSWER
Answered 2022-Jan-24 at 08:37Solution 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())
}
Community Discussions, Code Snippets contain sources that include Stack Exchange Network
No vulnerabilities reported
Save this library and start creating your kit
Explore Related Topics
Save this library and start creating your kit