如何为调用多次不同方法的 Golang 函数模拟单元测试 AWS 服务?

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

How to mock unit tests for Golang function calling an AWS Service multiple times with different methods?

问题

我有以下函数,它会多次调用 AWS IAM。我可以对单个调用运行单元测试。但是当我对下面的测试运行时,我得到一个 panic 错误:"runtime error, invalid memory or nil pointer dereference"

func (iamDependency *iamService) CreateMyUser(userName string) (string, error){

  // 第一个 IAM 调用
  err := iamDependency.GetUser(&iam.GetUserInput{UserName: userName})

  if err != nil {
    fmt.Println("Failed to get user, attempting to create")
    
    // 第二个 IAM 调用
    err := iamDependency.CreateUser(&iam.CreateUserInput{UserName: userName})

    if err != nil {
      log.Fatalf("Failed to create user\n", err)
    }
  }
}

这是我的模拟和测试代码:

type mockSomeOutput{}

type mockedIamCall struct {
  iamiface.IAMAPI
  Response mockSomeOutput
}

func TestCreateMyUser(t *testing.T) {
  t.Run("Successful user create", func(t *testing.T) {
    mo := mockOutput{}

    m := mockedIamCall{Response: mo}

    d := iamService{
      iamInstance: m,
    }

    mockedUser := "TestUser"

    _, err := d.ResetCredentials(&mockedUser)

    if err != nil {
      t.Fatal("Everything should be ok")
    }
  })
}

我想知道在 Golang 中对这种类型的函数进行单元测试是否有任何技巧或指导方针。

感谢任何帮助。

英文:

I have the following function that makes multiple calls to AWS IAM. I am able to run unit tests on single calls. However when I run a test on the one below I get a panic: "runtime error, invalid memory or nil pointer dereference"

  func (iamDependency *iamService) CreateMyUser(userName string) (string, error){

    //first IAM call
    err:=iamDependency.GetUser(&iam.GetUserInput{UserName: userName})

    if err != nil {

      fmt.Println("Failed to get user, attempting to create")
      
      //second IAM call
      err:=iamDependency.CreateUser(&iam.CreateUserInput{UserName: userName})
  
      if err != nil {
        log.Fatalf("Failed to create user\n", err )
      }
  }
}

Here is my mock and test:

type mockSomeOutput{}

type mockedIamCall{
  iamiface.IAMAPI
  Response mockSomeOutput
}

func TestCreateMyUser(t *testing.T){
  t.Run("Successful user create", fun(t *testing.T){
    mo:= mockOutput{}

    m:= mockedIamCall{Response: mo}
    
    d:= iamService{
      iamInstance: m,
    }

   mockedUser:="TestUser"
   
   _, err:= d.ResetCredentials(&mockedUser)
   
   if err != nil {
     t.Fatal("Everything should be ok")
   }
  })
}

I'm wondering whether there are any tricks or guidelines for making unit tests for this kind of function in Golang.

Appreciate any help.

答案1

得分: 1

你可能想尝试使用:https://github.com/golang/mock

你可以为iamiface.IAMAPI(从实际接口)创建模拟实现,然后期望函数调用并模拟响应。

使用mockgen创建接口的模拟实现。

mockgen -source={IAM API接口路径}

然后你可以在测试用例中使用类似以下方式来期望函数调用:

func TestExample(t *testing.T) {
  ctrl := gomock.NewController(t)
  mockIAMAPI := mock_package.NewMockIAMAPI(ctrl)
  mockIAMAPI.EXPECT().GetUser(expectedInput).Return(mockResponse).Times(1)
}
英文:

You probably want to try using: https://github.com/golang/mock

You can creating mock implementation for the iamiface.IAMAPI (from the actual interface) then expecting the function calls and mocking the response.

Creating the mock implementation of the interface using mockgen.

mockgen -source={path to IAM API interface}

And then you can expect the function calls with something like this on the test cases:

function TestExample(t *testing.T) {
  ctrl := gomock.NewController(t)
  mockIAMAPI := mock_package.NewMockIAMAPI(ctrl)
  mockIAMAPI.EXPECT().GetUser(expectedInput).Return(mockResponse).Times(1)
}

答案2

得分: 0

@Raymond感谢您的回复,很有见地。然而,我似乎已经找到了一个更简单的答案来回答我的问题。我创建了自己的接口:

type UserCreator interface {
  GetUser(*iam.GetUserInput) (*iam.GetUserOutput, error)
  CreateUser(*iam.CreateUserInput) (*iam.CreateUserInput, error)
}

func CreateMyUser(iamSvc UserCreator, userName string) (string, error) {
    // 第一个 IAM 调用
    _, err := iamSvc.GetUser(&iam.GetUserInput{UserName: userName})
    if err != nil {
        fmt.Println("获取用户失败,尝试创建用户")
        // 第二个 IAM 调用
        _, err := iamSvc.CreateUser(&iam.CreateUserInput{UserName: userName})
        if err != nil {
            log.Fatalf("创建用户失败\n", err)
        }
    }
}

然后,为了进行测试,我只需实现该接口,重写这些方法,并传入一个模拟对象:

type mockUserCreator struct {
  Response string
}

func (m *mockUserCreator) GetUser(input *iam.GetUserInput) (*iam.GetUserOutput, error) {
    return &iam.GetUserOutput{}, nil
}

func (m *mockUserCreator) CreateUser(input *iam.CreateUserInput) (*iam.CreateUserOutput, error) {
    return &iam.CreateUserOutput{}, nil
}

func TestCreateMyUser(t *testing.T) {
    testcases := []struct {
        TestName string
    }{
        {
            TestName: "Some test",
        },
    }
    for _, tt := range testcases {
        t.Run(tt.TestName, func(t *testing.T) {
            m := mockUserCreator{}
            mockUser := "TestUser"
            _, err := CreateMyUser(&m, mockUser)
            if err != nil {
                t.Error("TestCreateMyUser 返回了一个错误:%s", err)
            }
        })
    }
}

以上是您要翻译的内容。

英文:

@Raymond thanks for the response, it was insightful. However I seem to have found a simpler answer to my own question. I created my own interface

type UserCreator interface{
  GetUser(*iam.GetUserInput) (*iam.GetUserOutput, error)
  CreateUser(*iam.CreateUserInput) (*iam.CreateUserInput, error)
}


func CreateMyUser(iamSvc UserCreator, userName string) (string, error){

    //first IAM call
    _, err:=iamSvc.GetUser(&iam.GetUserInput{UserName: userName})

    if err != nil {

      fmt.Println("Failed to get user, attempting to create")
      
      //second IAM call
      _, err:=iamSvc.CreateUser(&iam.CreateUserInput{UserName: userName})
  
      if err != nil {
        log.Fatalf("Failed to create user\n", err )
      }
  }
}

And then for my test I just implement the interface, override these methods, and pass a mock:

type mockUserCreator{
  Response string
}

func (m * mockUserCreator) GetUser(input *iam.GetUserInput)(*iam.GetUserOutput, error){
return &iam.GetUserOutput{}, nil
}

func (m * mockUserCreator) CreateUser(input *iam.CreateUserInput)(*iam.CreateUserOutput, error){
return &iam.CreateUserOutput{}, nil
}

func TestCreateMyUser(t *testing.T){
  testcases:=[]struct{
   TestName string
  }{
    {
      TestName:"Some test"
    }
  }
  for _, tt := range testcases{
    t.Run(tt.TestName, func(t *testing.T){
      m := mockUserCreator{}

     mockUser := "TestUser"

      _, err:= CreateMyUser(&m, mockUser)
      
      if err != nil {
        t.Error("TestCreateMyUser returned and error: %s", err)
      }

    }
  }
}

huangapple
  • 本文由 发表于 2021年11月18日 05:55:08
  • 转载请务必保留本文链接:https://go.coder-hub.com/70012060.html
匿名

发表评论

匿名网友

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

确定