Building a Unit-Testable API in Golang

Composing a testable API in Golang is simple. This post covers an example API using the power of interfaces to create test fakes and true single-unit tests.

I’ll spare you the about me section. Check out http://kwp.io/me if you want to know more. This article was inspired by a post on effective unit testing by Nathan LeClaire. Thanks to Nathan for the inspiration.


While building an API in Golang can be as simple as reading the net/http docs, you can make your life (and the rest of your development team’s lives) a bit less stressful using the power of interfaces and fakes to compose a true single-unit testable API. This post covers building and unit testing a simple example API. The focus here is on testing the API. Some things that are important (logging, context, recovery, …) are excluded for simplicity. If you prefer to just read the code it’s all at kpurdon/go-api-example.


Let’s Begin!

The API example presented here is simple. It exposes a single endpoint that can be used to retrieve a list of public GitHub repos for a given username.

GET /repos?user=<github_username>
curl “localhost:8080/repos?user=kpurdon"
[
{
“name”: “go-api-example”,
“description”: “An example API”
},
{
“name”: “go-presentations”,
“description”: “Presentations on Golang (built for go presenter)”
},

]

The complete project layout is also very simple. First, it contains a main package in the root where the server is initialized and http handlers are defined. Second, an internal package, called repos, where functions for interacting with the GitHub Repos API are defined.

├── internal
│ └── repos - GitHub API interaction
│ ├── repos.go
│ └── repos_test.go
├── main.go - Server/Handler Initialization
└── main_test.go

The goal of this post will be to cover the unit testing of both packages in the project, avoiding calls to external services (GitHub API) and calls to any other internal functions during tests. In other words the goal is to test single units. Many programming languages solve the single-unit testing problem using the concept of mocking. An alternative approach (tuned to Golang) will be shown here: using interfaces and test fakes.


Faking Internal Functions

Let’s first look at the http handler function defined in the main package. It’s a standard net/http handler function that makes a call to the internal repos package. In order to effectively unit test this http handler we will want to avoid calling functions in the repos package.

In order to avoid the repos.Get function call we’ll employ an interface defined in the repos package which exposes the Get function. We’ll then be able to define both a real struct and test struct that fulfill this interface.

The first step in this process is to define a main package struct (App in this case) that contains the repos Client interface. This will allow us to initialize a real client in our app and a test client in our test.

type App struct {
repos repos.Client
}
func (a *App) GetReposHandler(...) {
...
repos, err := a.repos.Get(user)
...
}

Next, the definition of the Client interface and the implementation of the real ReposClient used in the application.

type Client interface { 
Get(string) ([]Repo, error)
}
type ReposClient struct{} 
func (c ReposClient) Get(user string) ([]Repo, error) {
// call GitHub api
}

Client is an interface and our real Get function is defined on ReposClient, a struct that fulfills the Client interface. We use this same pattern to define a TestClient and fake Get function. Note the difference between the real ReposClient and ReposTestClient is that we define two field (Repos and Err) on the test client. These will be used later in the tests to define expected output when we initialize a ReposTestClient.

type ReposTestClient struct { 
Repos []repos.Repo
Err error
}
func (c ReposTestClient) Get(string) ([]repos.Repo, error) { 
return c.Repos, c.Err
}

We can now use this ReposTestClient and the fake Get function in our GetReposHandler tests by defining an App struct that uses the ReposTestClient instead of the real repos.ReposClient.

Note that the tests also use the testify/assert package for simple assertions.


Mocking External HTTP Calls

In order to unit test the repos.Get function we will have to avoid calls to the GitHub API. There are a few different options for mocking external HTTP calls in Go tests but I found the jarcoal/httpmock package to be the simplest solution. Since this section is just using an external package I wont cover it in detail, rather suggest that you read the docs and look at the test defined below.

Note that the underlying principle of httpmock is hijacking the net/http DefaultTransport which some might find troublesome. Technically it’s possible for net/http to be updated and break the httpmock package (though unlikely).

TestGet

Wrapping Up

I hope that this example has expanded on the great work by Nathan Leclaire. Again, the source is available on GitHub.


Want to work on cool projects and have time to explore topics like this one? Come work with me and the rest of the awesome engineering team at bitly.is/hiring.