Check dynamic value for field in struct in go using testify

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

Check dynamic value for field in struct in go using testify

问题

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

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

Service层代码

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或忽略测试中结构体中的值?

英文:

I have a user service that validates the user data and formats it and then calls Firebase service which creates a firebase user and return firebase id and then it pass that user data to repository layer. My user struct has an ID field is populated by uuid in user service before passing to repository layer. I am mocking the firebase service and repository layer using strecher/testify. But the test is failing since the ID is populated by service layer and I cannot pass the ID to the user data used by mock function.

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

Service layer code

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

}

Test code

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

Error while testing

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]

Is there any way I could check the dynamically created uuid or ignore the values in the struct in the test?

答案1

得分: 3

如果你不想考虑 mockFirebaseAuthClient.On("CreateUser", user).Return("firebaseuniqueid", nil)mockPostgresRepo.On("CreateUser", user).Return(nil),只想模拟这些调用,那么你可以在这两个调用中使用 mock.Anything 作为参数,而不是 user,像这样 mockFirebaseAuthClient.On("CreateUser", mock.Anything).Return("firebaseuniqueid", nil)。这样参数将不会被考虑,模拟调用将返回所需的值。

英文:

if you don't want to consider mockFirebaseAuthClient.On("CreateUser", user).Return("firebaseuniqueid", nil) and mockPostgresRepo.On("CreateUser", user).Return(nil) and just want to mock that calls, then you can use mock.Anything as the argument in both the calls instead of user like this mockFirebaseAuthClient.On("CreateUser", mock.Anything).Return("firebaseuniqueid", nil) . So the arguments will not be considerd and the mock calls will return required value.

答案2

得分: 0

关于你的问题:

> 由于uuid没有注入到服务层,它如何被模拟?它是一个导入的包。

首先,定义一个具有我们想要模拟的相同方法的接口:

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
}

现在我们可以这样编写测试,看看这两个方法如何接受实现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())  // 输出 test
        t.Log("Real UUID UUID:", realUUID.String()) // 输出一个 UUID
    })
}

使用go test -v运行它以查看t.Log(...)的输出结果。

英文:

Regarding your question of

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

Like this, first, define an interface with the same method we want to mock

type uuidGen interface {
	String() string
}

Then, define a mock type in which we're going to define our method

type mockGen struct{}

Then, define the method on the type

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

Update CreateUser function to receive a uuidGen parameter that shares the method String() with uuid's package.

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
}

Now we can write the test like this, see how the 2 methods accept different types that implement the interface uuidGen and can call a method 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
	})
}

Run it with go test -v to see the output of t.Log(...)

huangapple
  • 本文由 发表于 2021年12月19日 19:23:43
  • 转载请务必保留本文链接:https://go.coder-hub.com/70410983.html
匿名

发表评论

匿名网友

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

确定