I have this function that I wanna test. I use testify to create the unit test.
// calculate.go
import (
"context"
"errors"
"testing"
"github.com/jmoiron/sqlx"
)
func (u usecase) CalculateTransaction(ctx context.Context, ID int64) (err error) {
// tx has the type of `*sqlx.Tx`, which I got from that imported package above
tx, err := u.sqlRepo.BeginTX()
if err != nil {
return err
}
defer func() {
if tx != nil {
err = tx.Rollback()
if err != nil {
return err
}
}
}
err = tx.Commit()
if err != nil {
return err
}
return nil
}
This is the supposed to be unit test
// calculate_test.go
func TestCalculate(t *testing.T) {
var tx *sqlx.Tx
var expectedError string
mockSQL = new(mocks.SQLRepository)
mockSQL.On("BeginTX").Return(tx, nil)
// mock tx.Commit() here
// mock tx.Rollback() here
usecase := &calculationUsecase{
sqlRepo: mockSQL
}
actualError := usecase.CalculateTransaction(context.Background(), 1)
require.EqualError(t, actualError, expectedError)
}
My problem is with the tx.Commit()
and tx.Rollback()
methods. If I run it this way, I'd get a invalid memory address or nil pointer dereference
error, which makes sense because I haven't set up the expectations for those method calls.
But I have no idea how to create it. Those methods themselves are imported from external package, which is "github.com/jmoiron/sqlx"
. I'm not sure if I need to create a mock object for external package like this which is located on root directory or if there's another way to do it without having to mock it.
Anyone can tell me how to fix this problem or how to set up the expectations?
I don't know if I can do it, but I also tried combining the unit test (which uses testify) with DataDog's sqlmock like this
// calculate_test.go
func TestCalculate(t *testing.T) {
var tx *sqlx.Tx
var expectedError string
dbMock, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was not expected", err)
}
defer dbMock.Close()
mockSQL = new(mocks.SQLRepository)
mockSQL.On("BeginTX").Return(tx, nil)
mock.ExpectCommit()
mock.ExpectRollback()
usecase := &calculationUsecase{
sqlRepo: mockSQL
}
actualError := usecase.CalculateTransaction(context.Background(), 1)
require.EqualError(t, actualError, expectedError)
}
But it doesn't work either. It still gives the panic: runtime error: invalid memory address or nil pointer dereference
error at err = tx.Commit()
line.
CodePudding user response:
In your test, you mock DB on DB provider level, no more mocking is necessary.
Test without redundant mocking:
func TestCalculate(t *testing.T) {
// transaction is created inside `CalculateTransaction`, it will be started against mocked DB
// var tx *sqlx.Tx
var expectedError string
dbMock, mock, err := sqlmock.New()
if err != nil {
t.Fatalf("an error '%s' was not expected", err)
}
defer dbMock.Close()
// we use sql mock - no need to mock DB methods
// mockSQL = new(mocks.SQLRepository)
// mockSQL.On("BeginTX").Return(tx, nil)
mock.ExpectCommit()
// here you can add ExpectQuery or ExpectExec if usecase do any DB work
mock.ExpectRollback()
usecase := &calculationUsecase{
sqlRepo: dbMock
}
actualError := usecase.CalculateTransaction(context.Background(), 1)
require.EqualError(t, actualError, expectedError)
// it is good practice to check is all expectations are met
err = mock.ExpectationsWereMet()
require.NoError(t, err)
}