05 - Testing a Service Layer Method in Go Account Application
Tags: go, testing, tutorial, rest
Published: 2020-10-20 23:25:08

Last time, we looked at how we can test an HTTP handler for fetching a logged-in user's personal information (the "/me" endpoint). Today we'll add, and test, our first method in the service layer, the _Get_ method of the _UserService_.

We'll reuse many of the principles from the [last post](https://dev.to/jacobsngoodwin/04-testing-first-gin-http-handler-9m0), so I encourage you to reference it as needed! In fact, today's tutorial should be a breeze if you grasped that concepts in the last one.

If at any point you are confusion about file structure or code, go to [Github repository](https://github.com/JacobSNGoodwin/memrizr) and checkout the branch for the previous lesson to be in sync with me!

If you prefer video, check out the video version below!
{% youtube Y7mn_v3rpIg %}

And for an overview of what the crap we're building, see this.
{% youtube Ez2dOyxmGDE %}

## Overview

We previously mocked the _UserService_ when testing the _me_ method of the handler layer. However, to actually create a functioning application, the handler will require a concrete implementation of a _UserService_. That's what we're doing today!

![UserService Get](https://dev-to-uploads.s3.amazonaws.com/i/7ydivo48r7xuhsurr6q4.png)

As the _handler_ layer calls methods in the _service_ layer, the _service_ layer will call the _repository_ layer's methods. We also want to test _service_ layer methods in isolation of the _repository_ layer. Therefore, we will mock the _repository_ layer for our _service_ layer tests!

OK, ¡vamos!

## Create the UserService

Let's create a struct and factory method for initializing the *UserService* with its required dependencies.

We do so by adding a `~/service/user_service.go` file as shown below. We'll add the test file shown in the service folder later on.

![UserService File Structure](https://dev-to-uploads.s3.amazonaws.com/i/i20hqa2hi5nmq6aivak2.PNG)

```go
package service

import (
	"github.com/jacobsngoodwin/memrizr/model"
)

// UserService acts as a struct for injecting an implementation of UserRepository
// for use in service methods
type UserService struct {
	UserRepository model.UserRepository
}

// USConfig will hold repositories that will eventually be injected into this
// this service layer
type USConfig struct {
	UserRepository model.UserRepository
}

// NewUserService is a factory function for
// initializing a UserService with its repository layer dependencies
func NewUserService(c *USConfig) model.UserService {
	return &UserService{
		UserRepository: c.UserRepository,
	}
}
```

We do the following in the above code.

1. Declare the _UserService_ as part of the _service_ package. Some may choose to even add a sub-package inside of service based on the feature or "use case," in this case "users." We'll forgo this (unless I later change me mind 😂😥).
2. We then create a `UserService` struct that will hold dependencies for this service. So far, the only dependency for the method we'll be working on is the _UserRepository_, but we'll definitely add more dependencies later on!
3. We then create a _NewUserService_ factory function and an accompanying _USConfig_ (short for UserService Config) which will eventually be used to initialize a UserService. 

Take note that our _NewUserService_ function returns a `model.UserService`, which is an interface. This is because we want the concrete _UserService_ (what we're building now), to have all of the methods defined on the interface. This will allow our UserService implementation to later be injected into the handler layer.

You might currently have a red-squiggly line in your code editor. This is because we have yet to define a _Get_ method on our _UserService_, but the _model.UserService_ interface defines this as a required method.

## Implement the model.UserService Interface

To get rid of the above-mentioned error, let's implement a _Get_ method with the required signature. This will actually be pretty dad-gummed simple. We add the following to the currently-opened *user_service.go*.

```go
// Get retrieves a user based on their uuid
func (s *UserService) Get(ctx context.Context, uid uuid.UUID) (*model.User, error) {
	u, err := s.UserRepository.FindByID(ctx, uid)

	return u, err
}
```

That's all for the _Get_ method, folks! We simply reach out to the repository layer to get the User. Our logic is simple because we'll eventually validate the user making a request inside of middleware.

## Creating a Mock for UserRepository FindByID Method

I'm going to flat-out throw down the mock code here inside of the `~/model/mocks/user_repository.go` file. If you want a little more insight on how this mock records method calls, check out the [previous post](https://dev.to/jacobsngoodwin/04-testing-first-gin-http-handler-9m0) or the [testify documentation](https://github.com/stretchr/testify).

![UserRepository Mock File](https://dev-to-uploads.s3.amazonaws.com/i/tqdebtvk54pmuhq3ujwb.PNG)

```go
package mocks

import (
	"context"

	"github.com/google/uuid"
	"github.com/jacobsngoodwin/memrizr/model"
	"github.com/stretchr/testify/mock"
)

// MockUserRepository is a mock type for model.UserRepository
type MockUserRepository struct {
	mock.Mock
}

// FindByID is mock of UserRepository FindByID
func (m *MockUserRepository) FindByID(ctx context.Context, uid uuid.UUID) (*model.User, error) {
	ret := m.Called(ctx, uid)

	var r0 *model.User
	if ret.Get(0) != nil {
		r0 = ret.Get(0).(*model.User)
	}

	var r1 error

	if ret.Get(1) != nil {
		r1 = ret.Get(1).(error)
	}

	return r0, r1
}
```

We add a single method, _FindByID_ to the mock, as this is currently the only method defined on the _model.UserRepository_ interface. This is the method which is called from our _service.UserService.Get_ method, so the above code will allow us to determine if the FindByID method has been called with a particular set of parameters.

## Add Tests

We'll now add `~/service/user_service_test.go` as previously shown. In the file, we add two cases cases, a successful call to the _UserRepository_ which returns a _user_, and a call to the _UserRepository_ which produces an error. 

We'll create a single outer test function called _TestGet_. Inside of this function, we'll add the two test cases inside of `t.Run` methods.

```go
func TestGet(t *testing.T) {
	// Setup
	gin.SetMode(gin.TestMode)

  // add two cases here
}
```

#### Success Case

Inside of the success case, we do the following:

1. Create a _mockUserResp_ to be returned from our call to the UserRepository's _FindByID_ method. 
2. Instantiate a _mockUserRepository_ from the previously created mock.
3. We inject this _mockUserRepository_ implementation into our UserService with the _NewUserService_ factory we recently created.
4. Tell the _mockUserRepository_ to respond with the _mockUserResp_ when _FindByID_ is called with the _uid_ we created.
5. Create an empty context with _context.TODO_. For this test, we're not concerned with what is inside of the context, but we do require a non-nil context in our _Get_ method call. That's precisely what _context.TODO_ produces!
6. Call the _Get_ method and assert that the response is the _mockUserResp_ for the first return value and _nil_ for the error.
7. Assert that the "FindByID" method was called with the _AssertExpectations_ method.

```go
t.Run("Success", func(t *testing.T) {
  uid, _ := uuid.NewRandom()

  mockUserResp := &model.User{
    UID:   uid,
    Email: "bob@bob.com",
    Name:  "Bobby Bobson",
  }

  mockUserRepository := new(mocks.MockUserRepository)
  us := NewUserService(&USConfig{
    UserRepository: mockUserRepository,
  })
  mockUserRepository.On("FindByID", mock.Anything, uid).Return(mockUserResp, nil)

  ctx := context.TODO()
  u, err := us.Get(ctx, uid)

  assert.NoError(t, err)
  assert.Equal(t, u, mockUserResp)
  mockUserRepository.AssertExpectations(t)
})
```

#### Error Cases

We do the same as above, but this time, we tell the mock to return an error when _FindByID_ is called. We then assert that the first return value of _Get_ is nil (no user returned), and the second return value is an error. Lastly, we assert that _FindByID_ was, indeed, called. 

```go
t.Run("Error", func(t *testing.T) {
  uid, _ := uuid.NewRandom()

  mockUserRepository := new(mocks.MockUserRepository)
  us := NewUserService(&USConfig{
    UserRepository: mockUserRepository,
  })

  mockUserRepository.On("FindByID", mock.Anything, uid).Return(nil, fmt.Errorf("Some error down the call chain"))

  ctx := context.TODO()
  u, err := us.Get(ctx, uid)

  assert.Nil(t, u)
  assert.Error(t, err)
  mockUserRepository.AssertExpectations(t)
})
```

## Run Tests

Running the tests with `go test -v ./service/...` produces the following response, showing our passing tests!

```bash
=== RUN   TestGet
=== RUN   TestGet/Success
    user_service_test.go:41: PASS:      FindByID(string,uuid.UUID)
=== RUN   TestGet/Error
    user_service_test.go:59: PASS:      FindByID(string,uuid.UUID)
--- PASS: TestGet (0.00s)
    --- PASS: TestGet/Success (0.00s)
    --- PASS: TestGet/Error (0.00s)
PASS
ok      github.com/jacobsngoodwin/memrizr/service       (cached)
```

As always, I encourage you to "fail" the tests, and perhaps even add some new tests to make sure this method is functioning and being tested properly.

## Next Time

Thanks again for following along! Next, time, we'll direct our attention to adding a handler for logging in a user with email address and password. The handler and testing will have a lot in common with the handler we've already tested, except this time we'll actually need to validate the incoming email and password included in the request body.

¡Hasta luego!