使用 testify 检查结构中字段的动态值

Check dynamic value for field in struct in go using testify

我有一个 user service 来验证用户数据并对其进行格式化,然后调用创建 firebase 用户和 return firebase id 的 Firebase 服务,然后将该用户数据传递给 repository层。我的用户结构有一个 ID 字段,在传递到 repository 层之前由 user service 中的 uuid 填充。我正在使用 strecher/testify 模拟 firebase 服务和存储库层。但是测试失败了,因为 ID 由服务层填充,我无法将 ID 传递给模拟函数使用的用户数据。

user := model.User{
        ID:         "",
        FirebaseID: "",
        FirstName: "Test",
        LastName:  "User",
        FullName:  "Test User",
        Email:     "testuser@email.com"
        Password: "password",
    }

服务层代码

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

}

测试代码

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

测试时出错

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]

有什么方法可以检查动态创建的 uuid 或忽略测试中结构中的值吗?

如果您不想考虑 mockFirebaseAuthClient.On("CreateUser", user).Return("firebaseuniqueid", nil)mockPostgresRepo.On("CreateUser", user).Return(nil) 并且只想模拟该调用,那么您可以在两个调用中使用 mock.Anything 作为参数而不是user 这样 mockFirebaseAuthClient.On("CreateUser", mock.Anything).Return("firebaseuniqueid", nil) 。因此将不考虑参数,模拟调用将 return 所需值。

关于你的问题

Since uuid is not injected into the service layer, how can it be mocked? It is an imported package.

像这样,首先,用我们想要模拟的相同方法定义一个接口

type uuidGen interface {
    String() string
}

然后,定义一个模拟类型,我们将在其中定义我们的方法

type mockGen struct{}

然后,在类型上定义方法

func (u *mockGen) String() string {
    return "test"
}

更新 CreateUser 函数以接收与 uuid 的包共享方法 String()uuidGen 参数。

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
}

现在我们可以这样写测试了,看看这2个方法如何接受实现接口uuidGen的不同类型,并且可以调用一个方法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
    })
}

运行 它与 go test -v 一起看 t.Log(...)

的输出