Unit Testing REST Services in Go

Building software is a complex process. A software project perceived to be simple when started can easily grow to having thousands of lines of code. Also, unforeseen circumstances are common occurrence for any software project and this can cause a lot of unplanned changes. Making changes to a software can be very hard and unpredictable, especially when it is not done carefully, there has been many real life instances where numerous problems or bugs were introduce to a software due to some changes made to it. This is also known as regression bug - a bug introduced as a result of a change or changes in a system. Writing automated tests is a great solution to minimising regression bug. Alongside with preventing regression bug, here are some other benefits of automated testing.

Of course, you can read more about the numerous benefits you can acquire by writing automated tests - https://www.testim.io/blog/test-automation-benefits/

One aspect of software development cycle where there’s less learning resources is automated testing. I personally had problems understanding why automated testing is needed and most importantly, how to do it right. Why i have to write code to test my code? Sounds elegant but some things don’t really make sense until you see how much values they bring.

In this article, we’re going to see how to write automated tests for Go REST services. Hopefully, someone out there would find it useful!

Assumptions

I assume you have basic knowledge of how HTTP request & response works, Golang knowledge is not essential as you can apply the principles in other languages, although many Golang specific library and semantics are used, it shouldn’t be too hard to take the principles and use them in other programming environments.

So what are Unit tests?

Before we dive in, i think it’s important to understand what Unit tests are. A Unit test is similar to what the name suggests, it is written to test a unit section or function in a particular software program, it is done to make sure a unit/function in a program behaves as expected, it is often written to mimic multiple functional scenarios ensuring the program unit behaves as expected when tested under all these scenarios. One important characteristics of Unit tests is Speed. Unit tests should run very quickly because the programmer would run them multiple times during the course of development.

The net/http/httptest Package

The Go standard library does not only provide rich packages to write http servers but it also provide rich packages to test them. The best place to look when looking to test REST services in Go is in the net/http/httptest package. It provides a hassle free components to test http handlers. As we’ll soon see, this package provide a way to create http.Request and http.ResponseWriter object which are important parts of any Go http handler. You can read more about this package HERE - GoDoc

Golang Interfaces

Don’t worry - I am not moving away from our topic. Interfaces in Golang are essential when writing tests - especially unit tests. They provide an excellent way to abstract concrete implementation of a logical function. Consider an example below:

 1type Greeter interface {
 2    Greet(name string) (string, error)
 3}
 4
 5type realGreeter struct {
 6    c RestClient
 7} 
 8func (r *realGreeter) Greet(name string) (string, error) {
 9    if r.c.Authorized(name) {
10        return "Hello " + name, nil
11    }
12    return "", errors.New("You're not allowed to be greeted")
13}
14
15type fakeGreeter struct {}
16func (r *fakeGreeter) Greet(name string) (string, error) {
17   return "Hi, Mr. " + name
18}
19
20type GreeterEngine struct {
21    greeter Greeter
22}
23
24func main() {
25    env := os.Getenv("ENV")
26    g := &realGreeter{}
27    if env == "local" {
28        g = &fakeGreeter{}
29    }
30    engine := &GreeterEnginer{greeter: g}
31    r, err := engine.greeter.Greet("Robot")
32    assert.Nil(err)
33    assert.Equals(r, "Hi, Mr. Robot")
34}

Long piece of code, i know and i wish i could make it shorter :(

The main point to be noted in the code snippet above is how we switch the implementation of a Greeter in a GreeterEngine based on the environment our code is executed in. This is a very useful trick when writing test, Unit test is about control - you should be able to control scenarios and outcomes because that is how you get power to test your systems as you see fit. The above example can also be related to a real life case where you can switch a real implementation of a function(e.g a function that makes HTTP or database calls as part of it operation) to a fake function(e.g a function that mocks HTTP or database calls). As we’ll soon see later in this post, interfaces are used heavily when mocking service dependencies which is one of the most important aspect of unit testing.

Mocking

Mocking is the process of creating objects and functions that simulates or mimics the behavior of real or actual objects/functions. REST services code structures are often complex with many dependencies. One of the best way to isolate these dependencies without too much hassles and unnecessary resources wasting is mocking. Let’s assume you need to test an API endpoint that accepts user details, performs data transformation and finally, persist the data. Your responsibility as a unit tester is to make sure you successfully accept, process and make calls to the database to persist the data, whether database connection succeeds or not is NOT your responsibility which is why you would mock this aspect. Unit testing is also about assumption, putting it mildly – Assuming database connection state is X, then API behavior should be Y

Enough talking, show me the code!

An Example

We are going ahead to create a simple sample project to demonstrate testing REST services. We want a simple system that creates blog posts via an endpoint plus another endpoint that retrieves the created posts – we are picking a well understood example simply because it’ll allow us focus more on how unit testing works rather than the example project itself.

Let’s dive in!

Quickly download and open the completed code for this project. It is hosted on GITHUB

We will first take a quick overview of the external packages we’ll be using in this project. Below is a list and a brief explanation of what each package is used for in this sample project.

 1package handlers
 2
 3import (
 4	"encoding/json"
 5	"github.com/adigunhammedolalekan/rest-unit-testing-sample/repository"
 6	"github.com/go-chi/render"
 7	"net/http"
 8)
 9
10type Handler struct {
11	repo repository.Repository
12}
13
14func New(repo repository.Repository) *Handler {
15	return &Handler{repo: repo}
16}
17
18func (handler *Handler) CreatePostHandler(w http.ResponseWriter, r *http.Request) {
19	var body struct{
20		Title string `json:"title"`
21		Body string `json:"body"`
22	}
23	if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
24		render.Status(r, http.StatusBadRequest)
25		render.Respond(w, r, &response{Success: false, Message: "malformed body"})
26		return
27	}
28
29	token := r.Header.Get("x-auth-token")
30	user, err := handler.repo.GetUser(token)
31	if err != nil {
32		render.Status(r, http.StatusForbidden)
33		render.Respond(w, r, &response{Success: false, Message: "forbidden"})
34		return
35	}
36
37	p, err := handler.repo.CreatePost(user.ID.String(), body.Title, body.Body)
38	if err != nil {
39		render.Status(r, http.StatusInternalServerError)
40		render.Respond(w, r, &response{Success: false, Message: "server error"})
41		return
42	}
43	render.Status(r, http.StatusOK)
44	render.Respond(w, r, &response{Success: true, Message: "post.created", Data: p})
45}

The code block above contains an http handler - CreatePostHandler. This handler is self explanatory but let’s see a detailed explanation below.

Line 19-22 defined a temporary struct to hold a Post data sent by a client. Line 23-27 decodes the request’s JSON body into our defined temp struct, this will return a 400 - Badrequest error when a malformed or non-JSON body is detected. Line 29-35 performs authorization check to ensure the user that’s about to create a post is allowed to do so. In this example, we used a fictional authentication and authorization system in order to keep the example simple and not take our eyes off the goal. And, finally Line 37-45 send the new Post to a persistence layer, error is returned if there were problems while interacting with the persistence layer or a success message is returned otherwise.

Let’s look at how to test this handler.

The first step is to generate mocks for our persistence layer, i explained previously how dependent services needs to be mocked in other to have control and test our service as we see fit – which is exactly what we are going to do here because database is a service we depend on. For this purpose, we will be using GoMock.

Change to the project root directory and run the command below(assuming you installed mockgen):

1mockgen -source=repo.go -destination=../mocks/repository_mock.go -package=mocks

The above command will generate mock implementations for repository/repo.go and put them in mocks directory/package. We will be using the generated code to mock our database dependency.

We’ll go ahead and separate our test code into three main parts - arrange, act and assert, this pattern is common and considered to be the best way to write good tests. Let’s take advantage of it!

Before that, let’s take a look at steps involved in each stage.

1w := httptest.NewRecorder()
2ctrl := gomock.NewController(t)
3defer ctrl.Finish()
 1    type body struct{
 2		Title string `json:"title"`
 3		Body string `json:"body"`
 4	}
 5
 6	mockToken := uuid.New().String()
 7	mockPost := &body{Title: "Test Title", Body: "Test Body"}
 8	mockUser := &types.User{ID: uuid.New(), Name: "Tester"}
 9	buf := &bytes.Buffer{}
10	err := json.NewEncoder(buf).Encode(mockPost)
11	assert.Nil(t, err)
12
13	r := httptest.NewRequest("POST", "/", buf)
14	r.Header.Add("x-auth-token", mockToken)
1    repo := mocks.NewMockRepository(ctrl)
2	repo.EXPECT().GetUser(mockToken).Return(mockUser, nil).Times(1)
3	repo.EXPECT().CreatePost(mockUser.ID.String(), mockPost.Title, mockPost.Body).Return(&types.Post{}, nil).Times(1)
1    handler := New(repo)
2	handler.CreatePostHandler(w, r)
3
4    assert.Equal(t, http.StatusOK, w.Code)
5    assert.True(t, strings.Contains(w.Body.String(), "post.created"))

Putting it all together,

 1
 2func TestHandler_CreatePostHandler(t *testing.T) {
 3
 4    // arrange
 5	w := httptest.NewRecorder()
 6	ctrl := gomock.NewController(t)
 7	defer ctrl.Finish()
 8
 9	type body struct{
10		Title string `json:"title"`
11		Body string `json:"body"`
12	}
13
14	mockToken := uuid.New().String()
15	mockPost := &body{Title: "Test Title", Body: "Test Body"}
16	mockUser := &types.User{ID: uuid.New(), Name: "Tester"}
17	buf := &bytes.Buffer{}
18	err := json.NewEncoder(buf).Encode(mockPost)
19	assert.Nil(t, err)
20
21	r := httptest.NewRequest("POST", "/", buf)
22	r.Header.Add("x-auth-token", mockToken)
23
24	repo := mocks.NewMockRepository(ctrl)
25	repo.EXPECT().GetUser(mockToken).Return(mockUser, nil).Times(1)
26	repo.EXPECT().CreatePost(mockUser.ID.String(), mockPost.Title, mockPost.Body).Return(&types.Post{}, nil).Times(1)
27
28    // act
29	handler := New(repo)
30	handler.CreatePostHandler(w, r)
31    
32    // assert
33	assert.Equal(t, http.StatusOK, w.Code)
34	assert.True(t, strings.Contains(w.Body.String(), "post.created"))
35}

Full code, including the 2nd endpoint(to get created posts) and it test can be found in the LINKED github repository.

Thanks for reading. I am actively taking feedback via email or my Twitter DM.

Happy coding :)