如何为我从外部包导入的方法创建单元测试的期望?

huangapple go评论68阅读模式
英文:

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

问题

我有一个要测试的函数。我使用testify来创建单元测试。

// calculate.go
import (
  "context"
  "errors"
  "testing"

  "github.com/jmoiron/sqlx"
)

func (u usecase) CalculateTransaction(ctx context.Context, ID int64) (err error) {
  // tx的类型为`*sqlx.Tx`,我从上面导入的包中获取到的
  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
}

这是应该进行的单元测试

// 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)

  // 在这里模拟tx.Commit()
  // 在这里模拟tx.Rollback()

  usecase := &calculationUsecase{
    sqlRepo: mockSQL
  }

  actualError := usecase.CalculateTransaction(context.Background(), 1)
  require.EqualError(t, actualError, expectedError)
}

我的问题出在tx.Commit()tx.Rollback()方法上。如果我按照这种方式运行,我会得到一个invalid memory address or nil pointer dereference错误,这是有道理的,因为我还没有设置这些方法调用的期望。

但是我不知道该如何创建它。这些方法本身是从外部包"github.com/jmoiron/sqlx"导入的。我不确定是否需要为这个位于根目录的外部包创建一个模拟对象,或者是否有其他方法可以在不进行模拟的情况下完成。

有人可以告诉我如何解决这个问题或如何设置期望吗?

我不知道是否可以这样做,但我还尝试将单元测试(使用testify)与DataDog的sqlmock结合起来,像这样:

// 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)
}

但它也不起作用。它仍然在err = tx.Commit()行处给出panic: runtime error: invalid memory address or nil pointer dereference错误。

英文:

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.

答案1

得分: 1

在你的测试中,你在DB提供程序级别上模拟了DB,不需要进行更多的模拟。

无冗余模拟的测试代码:

func TestCalculate(t *testing.T) {
  
  // 事务在`CalculateTransaction`内部创建,它将针对模拟的DB启动
  // var tx *sqlx.Tx
  var expectedError string

  dbMock, mock, err := sqlmock.New()
  if err != nil {
    t.Fatalf("不应该出现错误 '%s'", err)
  }
  defer dbMock.Close()

  // 我们使用sql模拟 - 不需要模拟DB方法
  // mockSQL = new(mocks.SQLRepository)
  // mockSQL.On("BeginTX").Return(tx, nil)
  
  mock.ExpectCommit()

  // 在这里,如果用例执行任何DB操作,可以添加ExpectQuery或ExpectExec

  mock.ExpectRollback()

  usecase := &calculationUsecase{
    sqlRepo: dbMock
  }

  actualError := usecase.CalculateTransaction(context.Background(), 1)
  require.EqualError(t, actualError, expectedError)

  // 检查是否满足所有期望是一个好习惯
  err = mock.ExpectationsWereMet()
  require.NoError(t, err)
}

以上是翻译好的内容,请确认是否满意。

英文:

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

huangapple
  • 本文由 发表于 2022年7月18日 01:52:56
  • 转载请务必保留本文链接:https://go.coder-hub.com/73014101.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定