Home > Enterprise >  How do I create an unit test expectation for a method that I imported from external package?
How do I create an unit test expectation for a method that I imported from external package?

Time:07-18

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)
}
  • Related