英文:
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(...)
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论