04 - Testing A Gin HTTP Handler With Testify Mock
Tags: go, testing, gin, tutorial
Published: 2020-10-09 22:37:38

Last time we discussed the application architecture and began building out models, errors, and interfaces inside of the _model_ package. Today we'll create our first handler, _me_, for accessing information of a currently logged in user. Furthermore, we'll unit test this handler by mocking the _UserService_ which the handler depends on. 

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

And if you prefer video, check out the video version below!

{% youtube y21NluUbvXo %}


## Prep Work

Between this lecture and the previous one, I decided to make a couple of minor changes.

#### Move _errors.go_ into _apperrors_ package

What you'll need to do is rename _errors.go_ to _apperrors.go_, then move this file into a new folder called _apperrors_ inside of _model_, as shown in the images below.

![Update Error Package](https://dev-to-uploads.s3.amazonaws.com/i/bia2lub8jo3p5xs6w0ep.png)

Finally, make sure to update the package name at the top of the file.

```go
package apperrors

...
```

#### Pass a Context Into Interfaces

After creating the original application for this tutorial, I decided it would be wise to pass a _Context_ from the go _context_ library into our service and repository methods.

For now, all you'll need to do is add `ctx context.Context` as the first argument to the methods as shown below. With this context passed down the call chain (handler -> service -> repository -> data sources), we can set a single time deadline by which this whole chain must be complete. 

```go
// UserService defines methods the handler layer expects
// any service it interacts with to implement
type UserService interface {
	Get(ctx context.Context, uid uuid.UUID) (*User, error)
}

// UserRepository defines methods the service layer expects
// any repository it interacts with to implement
type UserRepository interface {
	FindByID(ctx context.Context, uid uuid.UUID) (*User, error)
}
```

## Refactoring Handler Package

It's time to write our first handler, the _me_ handler, highlighted in the image below.

![Me handler](https://dev-to-uploads.s3.amazonaws.com/i/k07kp8yz9zn4qjzb08ur.png)

### Create me.go

First we'll move the _me_ handler method from _handler.go_ into it's own file called _me.go_. We'll eventually break all of our handlers into separate files in a similar manner. 

```go
package handler

import (
	"net/http"

	"github.com/gin-gonic/gin"
)

// Me handler calls services for getting
// a user's details
func (h *Handler) Me(c *gin.Context) {
	c.JSON(http.StatusOK, gin.H{
		"hello": "it's me",
	})
}
```

### Inject UserService Into Handler

Next, we will need our handler package to be able to work with a _UserService_, so we'll add this interface to our _handler_ struct definition, along with our _NewHandler_ function and config as follows. All our handler methods can now access any methods we define on the UserService interface!

```go
// Handler struct holds required services for handler to function
type Handler struct {
	UserService model.UserService
}

// Config will hold services that will eventually be injected into this
// handler layer on handler initialization
type Config struct {
	R           *gin.Engine
	UserService model.UserService
}

// NewHandler initializes the handler with required injected services along with http routes
// Does not return as it deals directly with a reference to the gin Engine
func NewHandler(c *Config) {
	// Create a handler (which will later have injected services)
	h := &Handler{
		UserService: c.UserService,
  }
  
  ...
}
```

## Updating me.go

We will update this file to do the following three tasks.

1. Check to make sure we have a valid user set on the request context, `*gin.Context`. We haven't yet created a way to do this, but we'll do so later by creating a middleware.
2. Call the _Get_ method on the UserService, which we'll need to inject into the handler package.
3. Send a JSON response, which will be a user if _Get_ is successful, or an _error_.

#### 1. Check For a Valid User

First, we make sure that our _c *gin.Context_ has a key called "user" on it. As I previously mentioned, we'll be able to extract this user from inside of a middleware and set the "user" value on the context. 

We then check to make sure there is a value on this key. In reality, our middleware will have already returned an Authorization error in this case, but we check for this case as an extra precaution (and what I assume is "good practice"). 

Finally, we cast the user to *model.User, and get the UID, which is the parameter required by the _UserService.Get_ method.

```go
// Me handler calls services for getting
// a user's details
func (h *Handler) Me(c *gin.Context) {
	// A *model.User will eventually be added to context in middleware
	user, exists := c.Get("user")

	// This shouldn't happen, as our middleware ought to throw an error.
	// This is an extra safety measure
	// We'll extract this logic later as it will be common to all handler
	// methods which require a valid user
	if !exists {
		log.Printf("Unable to extract user from request context for unknown reason: %v\n", c)
		err := apperrors.NewInternal()
		c.JSON(err.Status(), gin.H{
			"error": err,
		})

		return
	}

  uid := user.(*model.User).UID
  
  ...
```

#### 2. Call UserService.Get

This one is pretty simple. We don't yet have a concrete implementation of a UserService, so we'll learn how to mock its methods for testing soon!

```go
...

// gin.Context satisfies go's context.Context interface
u, err := h.UserService.Get(c, uid)
```

#### 3. Send a JSON Response

We check if _Get_ returned an error. If so, we'll log the complete error on our server. However, we'll just send a NotFound error in a JSON response to API consumers.

```go
...

  if err != nil {
    log.Printf("Unable to find user: %v\n%v", uid, err)
    e := apperrors.NewNotFound("user", uid.String())

    c.JSON(e.Status(), gin.H{
      "error": e,
    })
    return
  }

  c.JSON(http.StatusOK, gin.H{
    "user": u,
  })
}
```

## Testing me.go

### Mocking the UserService Get Method

Inside of the _model_ folder, let's create a _mocks_ folder with a single *user_service.go* file inside. 

![UserService mock](https://dev-to-uploads.s3.amazonaws.com/i/h4o0tdy3nkhbr3a6qetr.PNG)

We'll be making use of a pretty dadgummed awesome testing package called [testify](https://github.com/stretchr/testify) to create our mocks. So make sure to grab this package with `go get github.com/stretchr/testify`.

Inside of this file we create the mock as follows.

1. Add a struct for the MockUserService and add mock.Mock from the testify package which will give access to methods inside testify package.
2. Add a Get Method which implements the UserService interface, which currently has a single _Get_ method.
3. Inside of _Get_, we use mock.Mock methods to record what happens when calls are made to this method. Inside of the actual test, we can create an instance of MockUserService and write code (thanks to testify) which basically says _when we call Get with a particular context and uid, return the following values for *model.User and error_. The _m.Called_ method tracks the calls with particular parameter combinations. We then type cast the method responses to _*model.User_ and _error_, taking care to handle returning nil. With this mock created, we can determine if calls have been made to this mock user service inside of our tests.
  
*Note - I will be creating my tests manually for my experience, but there is a nice code generation tool called [mockery](https://github.com/vektra/mockery) that can build these mocks for you.*

```go
package mocks

import (
	"context"

	"github.com/google/uuid"
	"github.com/jacobsngoodwin/memrizr-tutorial-script/account/model"
	"github.com/stretchr/testify/mock"
)

// MockUserService is a mock type for model.UserService
type MockUserService struct {
	mock.Mock
}

// Get is mock of UserService Get
func (m *MockUserService) Get(ctx context.Context, uid uuid.UUID) (*model.User, error) {
	// args that will be passed to "Return" in the tests, when function
	// is called with a uid. Hence the name "ret"
  ret := m.Called(ctx, uid)

	// first value passed to "Return"
	var r0 *model.User
	if ret.Get(0) != nil {
		// we can just return this if we know we won't be passing function to "Return"
		r0 = ret.Get(0).(*model.User)
	}

	var r1 error

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

	return r0, r1
}

```

### Create Tests In me_test.go

First, make sure to create a *me_test.go* file inside of the handler package. Inside, we'll create a single test function, _TestMe_, which sets up gin to run in test mode, and then "runs" 3 separate cases, each defined inside of a t.Run() method. 

We'll test for the following cases:

1. Successful retrieval of a user - should return status 200 and JSON with a user.
2. No User provided on context - if we somehow make it through the middleware (not yet created) to this handler, and there's no user on the context, we should through an internal server error. 
3. If there is no user found with this id (unlikely), or some other error in the call chain, we'll expect to receive a 404, Not Found response. 

The complete code is included here, and I'll summarize the tests below.

```go
package handler

import (
	"encoding/json"
	"fmt"
	"net/http"
	"net/http/httptest"
	"testing"

	"github.com/stretchr/testify/mock"

	"github.com/gin-gonic/gin"
	"github.com/jacobsngoodwin/memrizr-tutorial-script/account/model/apperrors"
	"github.com/jacobsngoodwin/memrizr-tutorial-script/account/model/mocks"
	"github.com/stretchr/testify/assert"

	"github.com/google/uuid"

	"github.com/jacobsngoodwin/memrizr-tutorial-script/account/model"
)

func TestMe(t *testing.T) {
	// Setup
	gin.SetMode(gin.TestMode)

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

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

		mockUserService := new(mocks.MockUserService)
		mockUserService.On("Get", mock.AnythingOfType("*gin.Context"), uid).Return(mockUserResp, nil)

		// a response recorder for getting written http response
		rr := httptest.NewRecorder()

		// use a middleware to set context for test
		// the only claims we care about in this test
		// is the UID
		router := gin.Default()
		router.Use(func(c *gin.Context) {
			c.Set("user", &model.User{
				UID: uid,
			},
			)
		})

		NewHandler(&Config{
			R:           router,
			UserService: mockUserService,
		})

		request, err := http.NewRequest(http.MethodGet, "/me", nil)
		assert.NoError(t, err)

		router.ServeHTTP(rr, request)

		respBody, err := json.Marshal(gin.H{
			"user": mockUserResp,
		})
		assert.NoError(t, err)

		assert.Equal(t, 200, rr.Code)
		assert.Equal(t, respBody, rr.Body.Bytes())
		mockUserService.AssertExpectations(t) // assert that UserService.Get was called
	})

	t.Run("NoContextUser", func(t *testing.T) {
		mockUserService := new(mocks.MockUserService)
		mockUserService.On("Get", mock.Anything, mock.Anything).Return(nil, nil)

		// a response recorder for getting written http response
		rr := httptest.NewRecorder()

		// do not append user to context
		router := gin.Default()
		NewHandler(&Config{
			R:           router,
			UserService: mockUserService,
		})

		request, err := http.NewRequest(http.MethodGet, "/me", nil)
		assert.NoError(t, err)

		router.ServeHTTP(rr, request)

		assert.Equal(t, 500, rr.Code)
		mockUserService.AssertNotCalled(t, "Get", mock.Anything)
	})

	t.Run("NotFound", func(t *testing.T) {
		uid, _ := uuid.NewRandom()
		mockUserService := new(mocks.MockUserService)
		mockUserService.On("Get", mock.Anything, uid).Return(nil, fmt.Errorf("Some error down call chain"))

		// a response recorder for getting written http response
		rr := httptest.NewRecorder()

		router := gin.Default()
		router.Use(func(c *gin.Context) {
			c.Set("user", &model.User{
				UID: uid,
			},
			)
		})

		NewHandler(&Config{
			R:           router,
			UserService: mockUserService,
		})

		request, err := http.NewRequest(http.MethodGet, "/me", nil)
		assert.NoError(t, err)

		router.ServeHTTP(rr, request)

		respErr := apperrors.NewNotFound("user", uid.String())

		respBody, err := json.Marshal(gin.H{
			"error": respErr,
		})
		assert.NoError(t, err)

		assert.Equal(t, respErr.Status(), rr.Code)
		assert.Equal(t, respBody, rr.Body.Bytes())
		mockUserService.AssertExpectations(t) // assert that UserService.Get was called
	})
}
```

#### Test Success

In the success case, we create a _mockUserResp_ for UserService.Get to return. We then create an instance of our recently created MockUserService. We then tell this _mockUserService_ to respond to calls to the Get method having _uid_ with the _mockUserResp_ we just created. We don't require the mock to be concerned with the details of the first parameter, so we tell it to accept any call with a _*gin.Context_ by using the _mock.AnythingOfType_ method.

```go
mockUserService := new(mocks.MockUserService)
mockUserService.On("Get", mock.AnythingOfType("*gin.Context"), uid).Return(mockUserResp, nil)
```

We then add an httptest _ResponseRecorder_ which allows us to check that the HTTP response we receive is as expected.

We also create a handler with a _*gin.Engine_ and our _mockUserService_ injected into it. Note that we add a middleware to the router for this test, as shown below. This is how we set a User on the _*gin.Context_ to mock an actual user that will eventually be extracted in a middleware. 

```go
router.Use(func(c *gin.Context) {
    c.Set("user", &model.User{
      UID: uid,
    },
    )
  })
```

Next, create an HTTP request and then send the request to our router with the _ServeHTTP_ method. We then assert that we receive an HTTP 200 response with the expected JSON body. Finally, we check that our mockUserService was called inside of the handler with `mockUserService.AssertExpectations(t)`.

#### Test NoContextUser

This test includes a lot of the same code as the previous test. However, we don't add the User to the context with `router.Use(...)` as in the Success case.

Therefore, we expect an HTTP 500 status code. We also expect that the _mockUserService_ is not called since the response is sent before it has a chance to be called.

#### Test NotFound

This test is similar to the first two, but in this case we change the behavior or the _mockUserService_ to return an error when the _Get_ method is called. 

We then assert that the proper HTTP status code, 404, is returned along with a JSON response containing the error.

```go
mockUserService := new(mocks.MockUserService)
mockUserService.On("Get", mock.Anything, uid).Return(nil, fmt.Errorf("Some error down call chain"))
```

## Run the Tests

Inside of the account project folder, run _go test -v ./handler_ to test the package. If all has gone well, you should see output saying the test with the 3 sub-tests have passed. Make sure to try tweaking the tests (for example, the response codes) to make sure that they FAIL.

```sh
- PASS: TestMe (0.00s)
    --- PASS: TestMe/Success (0.00s)
    --- PASS: TestMe/NoContextUser (0.00s)
    --- PASS: TestMe/NotFound (0.00s)
```

## Next Time

Golly geez, today's tutorial was a lot! 

Next time, we'll take a bit of a breather and write code for the User Service which will repeat a lot of the principles we covered today.