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.
Related
I've been trying to write unit tests for my http handler. The code segment is as below:
func (s *Server) handleCreateTicketOption(w http.ResponseWriter, r *http.Request) {
var t ticket.Ticket
body, err := ioutil.ReadAll(r.Body)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
err = json.Unmarshal(body, &t)
if err != nil {
http.Error(w, er.ErrInvalidData.Error(), http.StatusBadRequest)
return
}
ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
res, err := json.Marshal(ticket)
if err != nil {
http.Error(w, er.ErrInternal.Error(), http.StatusInternalServerError)
return
}
log.Printf("%v tickets allocated with name %v\n", t.Allocation, t.Name)
s.sendResponse(w, res, http.StatusOK)
}
Actual logic that interacts with DB. This code segment is invoked by the handler as you can see in the code above. ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
func (t *TicketService) CreateTicketOption(ctx context.Context, ticket ticket.Ticket) (*ticket.Ticket, error) {
tx, err := t.db.dbPool.Begin(ctx)
if err != nil {
return nil, er.ErrInternal
}
defer tx.Rollback(ctx)
var id int
err = tx.QueryRow(ctx, `INSERT INTO ticket (name, description, allocation) VALUES ($1, $2, $3) RETURNING id`, ticket.Name, ticket.Description, ticket.Allocation).Scan(&id)
if err != nil {
return nil, er.ErrInternal
}
ticket.Id = id
return &ticket, tx.Commit(ctx)
}
And that is my unit test for the handler.
func TestCreateTicketOptionHandler(t *testing.T) {
caseExpected, _ := json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 10})
srv := NewServer()
// expected := [][]byte{
// _, _ = json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 20}),
// // json.Marshal(&ticket.Ticket{Id: 1, Name: "baris", Description: "test-desc", Allocation: 20})
// }
tt := []struct {
name string
entry *ticket.Ticket
want []byte
code int
}{
{
"valid",
&ticket.Ticket{Name: "baris", Description: "test-desc", Allocation: 10},
caseExpected,
http.StatusOK,
},
}
var buf bytes.Buffer
for _, tc := range tt {
t.Run(tc.name, func(t *testing.T) {
json.NewEncoder(&buf).Encode(tc.entry)
req, err := http.NewRequest(http.MethodPost, "/ticket_options", &buf)
log.Println("1")
if err != nil {
log.Println("2")
t.Fatalf("could not create request: %v", err)
}
log.Println("3")
rec := httptest.NewRecorder()
log.Println("4")
srv.handleCreateTicketOption(rec, req)
log.Println("5")
if rec.Code != tc.code {
t.Fatalf("got status %d, want %v", rec.Code, tc.code)
}
log.Println("6")
if reflect.DeepEqual(rec.Body.Bytes(), tc.want) {
log.Println("7")
t.Fatalf("NAME:%v, got %v, want %v", tc.name, rec.Body.Bytes(), tc.want)
}
})
}
}
I did research about mocking pgx about most of them were testing the logic part not through the handler. I want to write unit test for both handler and logic itself seperately. However, the unit test I've written for the handler panics as below
github.com/bariis/gowit-case-study/psql.(*TicketService).CreateTicketOption(0xc000061348, {0x1485058, 0xc0000260c0}, {0x0, {0xc000026dd0, 0x5}, {0xc000026dd5, 0x9}, 0xa})
/Users/barisertas/workspace/gowit-case-study/psql/ticket.go:24 +0x125
github.com/bariis/gowit-case-study/http.(*Server).handleCreateTicketOption(0xc000061340, {0x1484bf0, 0xc000153280}, 0xc00018e000)
/Users/barisertas/workspace/gowit-case-study/http/ticket.go:77 +0x10b
github.com/bariis/gowit-case-study/http.TestCreateTicketOptionHandler.func2(0xc000119860)
/Users/barisertas/workspace/gowit-case-study/http/ticket_test.go:80 +0x305
psql/ticket.go:24: tx, err := t.db.dbPool.Begin(ctx)
http/ticket.go:77: ticket, err := s.TicketService.CreateTicketOption(r.Context(), t)
http/ticket_test.go:80: srv.handleCreateTicketOption(rec, req)
How can I mock this type of code?
Create an interface which has the required DB functions
Your DB handler implements this interface. You use the handler in actual execution
Create a mock handler using testify/mock and use this in place of DB handler in test cases
From what I can read, you have the following structure:
type Server struct {
TicketService ticket.Service
}
type TicketService struct {
db *sql.Db // ..or similar
}
func (ts *TicketService) CreateTicketOption(...)
The trick to mock this is by ensuring ticket.Service is an interface instead of a struct.
Like this:
type TicketService interface {
CreateTicketOption(ctx context.Context, ticket ticket.Ticket) (*ticket.Ticket, error) {
}
By doing this, your Server expects a TicketService interface.
Then you could do this:
type postgresTicketService struct {
db *sql.Db
}
func (pst *postgresTicketService) CreateTicketOption(...)...
Which means that the postgresTicketService satisfies the requirements to be passed as a ticket.Service to the Server.
This also means that you can do this:
type mockTicketService struct {
}
func (mts *mockTicketService) CreateTicketOption(...)...
This way you decouple the Server from the actual implementation, and you could just init the Server with the mockTicketService when testing and postgresTicketService when deploying.
I have a user service that validates the user data and formats it and then calls Firebase service which creates a firebase user and return firebase id and then it pass that user data to repository layer. My user struct has an ID field is populated by uuid in user service before passing to repository layer. I am mocking the firebase service and repository layer using strecher/testify. But the test is failing since the ID is populated by service layer and I cannot pass the ID to the user data used by mock function.
user := model.User{
ID: "",
FirebaseID: "",
FirstName: "Test",
LastName: "User",
FullName: "Test User",
Email: "testuser#email.com"
Password: "password",
}
Service layer code
func (u userService) CreateUser(user model.User) error {
err := validateFields(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
user.FullName = user.FirstName + " " + user.LastName
user.FirebaseID, err = u.authClient.CreateUser(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
user.ID = uuid.NewString()
err = u.userRepo.CreateUser(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
return nil
}
Test code
func TestCreateUser(t *testing.T) {
mockFirebaseAuthClient := new(MockFirebaseAuthClient)
mockPostgresRepo := new(MockPostgresRepo)
userService := NewUserService(mockPostgresRepo, mockFirebaseAuthClient)
t.Run("Valid data", func(t *testing.T) {
user := model.User{
ID: "",
FirebaseID: "firebaseuniqueid",
FirstName: "Test",
LastName: "User",
FullName: "Test User",
Email: "testuser#email.com",
Password: "password",
}
mockFirebaseAuthClient.On("CreateUser", user).Return("firebaseuniqueid", nil)
mockPostgresRepo.On("CreateUser", user).Return(nil)
err := userService.CreateUser(user)
if err != nil {
t.Fatalf("Expectd: nil, got: %v", err)
}
})
Error while testing
mock: Unexpected Method Call
-----------------------------
CreateUser(model.User)
0: model.User{ID:"f87fd2f3-5801-4359-a565-a4eb13a6de37", FirebaseID:"firebaseuniqueid", FirstName:"Test", LastName:"User", FullName:"Test User", Email:"testuser#email.com", Password:"password"}
The closest call I have is:
CreateUser(model.User)
0: model.User{ID:"", FirebaseID:"firebaseuniqueid", FirstName:"Test", LastName:"User", FullName:"Test User", Email:"testuser#email.com", Password:"password"}
Difference found in argument 0:
--- Expected
+++ Actual
## -1,3 +1,3 ##
(model.User) {
- ID: (string) "",
+ ID: (string) (len=36) "f87fd2f3-5801-4359-a565-a4eb13a6de37",
FirebaseID: (string) (len=16) "firebaseuniqueid",
Diff: 0: FAIL: (model.User={f87fd2f3-5801-4359-a565-a4eb13a6de37 firebaseuniqueid Test User Test User testuser#email.com password}) != (model.User={ firebaseuniqueid Test User Test User testuser#email.com password}) [recovered]
Is there any way I could check the dynamically created uuid or ignore the values in the struct in the test?
if you don't want to consider mockFirebaseAuthClient.On("CreateUser", user).Return("firebaseuniqueid", nil) and mockPostgresRepo.On("CreateUser", user).Return(nil) and just want to mock that calls, then you can use mock.Anything as the argument in both the calls instead of user like this mockFirebaseAuthClient.On("CreateUser", mock.Anything).Return("firebaseuniqueid", nil) . So the arguments will not be considerd and the mock calls will return required value.
Regarding your question of
Since uuid is not injected into the service layer, how can it be mocked? It is an imported package.
Like this, first, define an interface with the same method we want to mock
type uuidGen interface {
String() string
}
Then, define a mock type in which we're going to define our method
type mockGen struct{}
Then, define the method on the type
func (u *mockGen) String() string {
return "test"
}
Update CreateUser function to receive a uuidGen parameter that shares the method String() with uuid's package.
func (u userService) CreateUser(uuid uuidGen, user User) error {
err := validateFields(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
user.FullName = user.FirstName + " " + user.LastName
user.FirebaseID, err = u.authClient.CreateUser(user)
if err != nil {
return fmt.Errorf("authClient CreateUser: %w", err)
}
user.ID = uuid.String()
err = u.userRepo.CreateUser(user)
if err != nil {
return fmt.Errorf("userService CreateUser: %w", err)
}
return nil
}
Now we can write the test like this, see how the 2 methods accept different types that implement the interface uuidGen and can call a method String()
func TestCreateUser(t *testing.T) {
mockFirebaseAuthClient := new(MockFirebaseAuthClient)
mockPostgresRepo := new(MockPostgresRepo)
userService := NewUserService("test", "test")
t.Run("Valid data", func(t *testing.T) {
user := User{
ID: "",
FirebaseID: "firebaseuniqueid",
FirstName: "Test",
LastName: "User",
FullName: "Test User",
Email: "testuser#email.com",
Password: "password",
}
mockFirebaseAuthClient.On("CreateUser", user).Return("firebaseuniqueid", nil)
mockPostgresRepo.On("CreateUser", user).Return(nil)
gen := new(mockGen)
err := userService.CreateUser(gen, user)
if err != nil {
t.Fatalf("Expectd: nil, got: %v", err)
}
realUUID := uuid.New()
err = userService.CreateUser(realUUID, user)
if err != nil {
t.Fatalf("Expectd: nil, got: %v", err)
}
t.Log("Mock UUID:", gen.String()) // prints test
t.Log("Real UUID UUID:", realUUID.String()) // prints a UUID
})
}
Run it with go test -v to see the output of t.Log(...)
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
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.
func loadDataFromDB() Data{
db, err := sql.Open("mysql","user:password#tcp(127.0.0.1:3306)/hello")
if err != nil {
log.Fatal(err)
}
defer db.Close()
rows, err := db.Query("select id, name from users where id = ?", 1)
if err != nil {
log.Fatal(err)
}
defer rows.Close()
// ... Parsing and returning
}
The connection should normally be injected into the function via parameters. How could I implement a unit test without modifying the code?
Use interface for DB related functions and implement it for testing with mock data.Please see the sample code below-
package app
import (
"errors"
errs "github.com/pkg/errors"
)
type DBSuccess struct {
}
func (d *DBSuccess) SaveGopher(g *Gopher) (string, error) {
return "successid", nil
}
func (d *DBSuccess) GetGopher(id string) (*Gopher, error) {
return &Gopher{
Id: id,
Name: "",
}, nil
}
type DBFailure struct {
}
func (d *DBFailure) SaveGopher(g *Gopher) (string, error) {
return "", errs.Wrap(errors.New("failure in saving to DB"), "failed in saving Gopher")
}
func (d *DBFailure) GetGopher(id string) (*Gopher, error) {
return nil, errs.Wrap(errors.New("failure in getting from DB"), "failed in fetching Gopher")
}