What is the most idiomatic way in Go to test code that depends on a structure with lots of methods?

Suppose I have a UserRepository structure that encapsulates the logic for interacting with a database. This structure has a set of methods such as:

  • FindAll ()
  • findById ()
  • FindByName ()
  • save ()
  • etc....

There is another structure (for example, call it UserService), which depends on the structure of UserRepository.

To test the UserService, I need to make fun of the functionality of UserRepository. The only way I know this is to provide an interface for the UserRepository and make the UserService dependent on it instead of the UserRepository structure. This will allow you to create a mock implementation of the interface and set it as a UserService dependency in the test.

What is the most idiomatic way to do this?

1) If the UserService depends on only 1 UserRepository method (say, findAll) - should I define an interface that will have all the repository methods, or is it better to define a separate interface only for this method and use it as a dependency on the UserService? If so, what is the best name for it (interface)? If another structure will depend on the findAll () and findById () methods, should I create another interface again?

2) Where is the best place to store mocks for these interfaces? Can they be reused? Or for tests of different structures do I need to override layouts?

PS since for me unit tests are a very important part of the project. I would like to make them as readable as possible by avoiding the template code and focusing on their logic. Therefore, creating several mock-up implementations for the same interfaces in different test files looks like a bad option for me, because it makes the test code less readable.

+6
source share
1 answer

1) I would go with what the elevator said, i.e. only requires the methods you need for this structure. For example, you have a UserService that needs FindByName and FindAll , and a UserAdminService that needs FindById , FindAll and Save . In this case, you should have two interfaces:

  • UserProvider with FindByName and FindAll
  • UserAdminProvider with FindById , FindAll and Save .

It also allows you to keep your UserProvider in check, for example. you know that it cannot invoke Save , therefore it cannot modify the user.

You will probably only need one real implementation that satisfies both interfaces.

2) Check testify / mock and mockery . Mockery will generate mocks for your interfaces in the mocks , one for each interface. This means that you cannot use the same mock structure for both tests, but that does not matter since the code is generated. You do not mock the behavior of the interface in the mock structure, you do this in the test, setting expectations, for example:

 func TestThatYouCantLoginWithNonexistentUser(t *testing.T) { userRepository := new(mocks.UserRepository) userService := user.NewService(userRepository) // if the userService calls UserRepository.FindByName("joe"), it will return nil, since there no such user. userRepository.On("FindByName", "joe").Return(nil) _, err := userService.Login("joe", "password") // err should not be nil and the message should be "user does not exist" assert.EqualError(t, err, "user does not exist") // assert that the expectations were met, ie FindByName was called with "joe" userRepository.AssertExpectations(t) } 

This really makes the test easier to understand, since you don’t need to check any other file, which makes the layout when you call FindByName("joe") .

+1
source

All Articles