Go: How to Mock Repositories with GoMock

Creating mock objects for data repositories is a common requirement in web application development. In this article, I will show you how to use GoMock to automatically generate mocks for your interfaces and use them inside unit tests.

When you have a service that depends on a repository to fetch some data, this repository will most likely be connected to a database. A typical sequence of events could look like this:

  • Service A requests an object X from repository B
  • Repository B connects to database C and fetches the requested rows
  • Repository B returns X in the appropriate format to Service A

But of course you want to write unit tests for Service A. The easiest solution would be to also rely on the database in your unit test, but this has multiple disadvantages (among them: the database needs to be consistent at all times for running the tests; the tests would be much slower; failing tests due to database problems would point in the wrong direction).

Mocking frameworks solve this problem by providing “fake” objects we can use instead of the original repositories. They do not connect to a database, but instead return hardcoded objects (in the simplest case).

A simple example – managing appointments

We will walk through this mocking process with a concrete example. Assume we want our Go application to track appointments (custom structs which have let’s say a start date, a first name and a last name; but we do not care about their specifics here). The appointments should be persisted to a relational database, and we use a repository to access this database.

Furthermore, there is an AppointmentService which performs business logic based on appointment objects. It uses the AppointmentRepository to fetch and persist the appointments.

To allow mocking, we split the AppointmentRepository into an interface (IAppointmentRepository) and implementation (AppointmentRepository). For now, only a single method is supported, GetAllAppointments(), which fetches all the persisted appointments from the database. Finally, we want to create a second implementation of IAppointmentRepository: a mock implementation named MockIAppointmentRepository. It will be autogenerated by GoMock and we can command it to return a specific set of appointments when the GetAllAppointments() method is invoked.

Summed up, we aim to create the following classes:

Installing GoMock and generating the mock objects

To get started, open a terminal, change the working directory to your project root and enter

go install github.com/golang/mock/[email protected]
which mockgen

This should install GoMock to your $GOPATH (make sure it is also in your path to make the downloaded mockgen file executable).

Depending on your $GOPATH, you will get a similar output:

/Users/bernhard/go/bin/mockgen

Next, we create the mock object by running the following command:

mockgen -source=interfaces/IAppointmentRepository.go -destination=interfaces/mocks/IAppointmentRepository.go -package=mocks

-source tells mockgen which interface we want to mock; -destination specifies the path to the newly generated file, and -package specifies the package name for the newly generated file.

Using the generated mock in a unit test

We are now ready to use the generated mock object inside a unit test. Create a file named AppointmentService_test.go with the following content:

package services

import (
  "github.com/golang/mock/gomock"
  "testing"
  "time"
  "mockExample/interfaces/mocks"
  "mockExample/models"
)

func TestAppointmentService_GetCountAppointments(t *testing.T) {
  mockCtrl := gomock.NewController(t)
  defer mockCtrl.Finish()
  mockAppointmentRepo := mocks.NewMockIAppointmentRepository(mockCtrl)
  mockAppointmentRepo.EXPECT().GetAllAppointments().Return([]models.Appointment{{
    Id:        1,
    Start:     time.Time{},
    FirstName: "Donald",
    LastName:  "Duck",
  }, {
    Id:        2,
    Start:     time.Time{},
    FirstName: "Daisy",
    LastName:  "Duck",
  }}, nil)

  appointmentService := AppointmentService{IAppointmentRepository: mockAppointmentRepo}
  appointmentCount, _ := appointmentService.GetCountAppointments()
  if appointmentCount != 2 {
    t.Errorf("AppointmentCount not matching")
  }
}

The unit test creates a mock object (mockAppointmentRepo) and specifies what it should return when the GetAllAppointments() method is invoked (in this case, it will return two hardcoded appointment instances).

Then the mock object is passed to the appointment service and the GetCountAppointments() method of the service is invoked. Internally, this uses the IAppointmentRepository and just returns the total count of appointments received from the repository.

Running the unit test

You can now run the generated test using go test – it should pass since the expected element count (two) is correct:

=== RUN   TestAppointmentService_GetCountAppointments
--- PASS: TestAppointmentService_GetCountAppointments (0.00s)
PASS

Process finished with the exit code 0

 

Bernhard Knasmüller on Software Development