Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What is the most idiomatic way in Go to test code which has dependency on structure with big amount of methods?

Lets say I have a UserRepository struct which incapsulates logic for interacting with a database. This struct has a set of methods like:

  • findAll()
  • findById()
  • findByName()
  • save()
  • and so on....

There is another struct (let's call it UserService for instance) which depends on UserRepository struct.

To test UserService I need to mock functionality of UserRepository. The only way I know to do this is to provide the interface for UserRepository and make UserService be dependent on it instead of UserRepository struct. It will allow to create a mocked implementation of interface and set it as dependency of UserService in the test.

What is the most idiomatic way to do it?

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

2) Where is a best place to store mocks for these interfaces? Is it possible to reuse them? Or for tests of different structs I will need redefine the mocks?

P.S. as for me unit tests is a very important part of the project. I would like to make them as readable as possible avoiding boilerplate code and focusing on their logic. So creating several mock implementations for same interfaces in different test files looks for me a bad option since it makes test code less readable.

like image 626
RhinoLarva Avatar asked Jul 29 '16 13:07

RhinoLarva


People also ask

How do you test a method in go?

At the command line in the greetings directory, run the go test command to execute the test. The go test command executes test functions (whose names begin with Test ) in test files (whose names end with _test.go). You can add the -v flag to get verbose output that lists all of the tests and their results.

Which command is used to run the tests in go?

Go comes with a testing package which provides support for automated testing of Go packages. The command go test automates the execution of any test function which is found in “*_test.go” files corresponding to the package under test.


1 Answers

1) I'd go with what elevine said, i.e. only require the methods that you need for that struct. So for example: you have UserService that needs FindByName and FindAll, and UserAdminService that needs FindById, FindAll and Save. In that case you should have two interfaces:

  1. UserProvider with FindByName and FindAll
  2. UserAdminProvider with FindById, FindAll and Save.

This also lets you keep your UserProvider in check, e.g. you know it can't call Save, so it can't modify a user.

You'll likely only need one actual implementation that satisfies both interfaces.

2) Check out testify/mock and mockery. Mockery will generate mocks for your interfaces in a mocks subpackage, one for each interface. This does mean you can't use the same mock struct for both tests, but it doesn't matter, because the code is generated. You don't mock up the behavior of the interface in the mock struct, you do it in the test by setting up expectations, e.g.:

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's 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, i.e. FindByName was called with "joe"
    userRepository.AssertExpectations(t)
}

This actually makes the test easy to understand, since you don't have to go check in some other file what the mock does when you call FindByName("joe").

like image 131
user1431317 Avatar answered Nov 15 '22 04:11

user1431317