3

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?

Amal Jose
  • 75
  • 8
  • You can mock the `uuid.NewString()` implementation to return the expected UUID, or perhaps use the [MatchedBy](https://pkg.go.dev/github.com/stretchr/testify@v1.7.0/mock#MatchedBy) to assert a valid UUID is set on your user model? – Emile Pels Dec 19 '21 at 12:28
  • Since `uuid` is not injected into the service layer, how can it be mocked? It is an imported package. – Amal Jose Dec 19 '21 at 14:15
  • Added an answer to your question in this comment @amljs :) can also be used as a solution to the question. Let me know if it doesn't work :) – Wolfgang Dec 19 '21 at 16:39
  • @Wolfgang Your answer works fine for checking both UUIDs. But it modifies the method signature which I don't want. What I wanted is either compare the UUIDs or ignore them. So I used `mock.Anything` to ignore the argument. thanks for your answer. – Amal Jose Dec 21 '21 at 06:29

2 Answers2

3

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.

albinjose
  • 153
  • 9
0

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(...)

Wolfgang
  • 1,328
  • 2
  • 8
  • 11