Dependency Injection in Golang

As it goes with learning most new languages, first attempts at creating applications in Go follow a natural progression. In this post we’ll follow this progression and build an app that has one purpose: send an HTTP request and print the response. Hopefully it will guide readers along the path to using dependency injection and understanding why it is, in my opinion, the most important concept when learning how to create complex, testable and maintainable code.

Phase 1: “Jam everything in main()”

In the first iteration of writing our application (the same way many people begin their adventure into go), we will place all of our app’s logic into main().

package main

import (
"fmt"
"net/http"
"io/ioutil"
)

func main() {
client := http.Client{}
response, err := client.Get("http://google.com")

if err != nil {
panic(err)
}

if response == nil {
fmt.Println("Received an empty response!")
return
}

body, err := ioutil.ReadAll(response.Body)

if err != nil {
fmt.Println("Couldn't read body of response!")
return
}

fmt.Println(string(body))
}

So far so good. If we run our app:

go run main.go

we receive a valid response body from Google. This is acceptable but we decide that we want our app to be slightly more dynamic, so let’s refactor it a bit to accept user input.

Phase 2: “Break it out into functions”

In our second phase, we decide we want to allow a URL entered by the user to make the user experience a little better. Also, we feel sending the HTTP request would be much more suited in it’s own function because a textbook we read somewhere said something about a single responsibility principle.

package main

import (
"fmt"
"net/http"
"io/ioutil"
"flag"
)

var (
url string
)

func init() {
flag.StringVar(&url, "url", "http://google.com", "Which URL do we want to parse?")
flag.Parse()
}

func main() {
err := send(url)

if err != nil {
panic(err)
}
}

func send(link string) error {
client := http.Client{}
response, err := client.Get(link)

if err != nil {
return err
}

if response == nil {
return err
}

body, err := ioutil.ReadAll(response.Body)

if err != nil {
return err
}

fmt.Println(string(body))
return nil
}

In this example we can now accept a URL as a flag and print the contents dynamically. We also receive the added benefit of better error handling as we can now bubble the errors up and allow the calling function to determine what needs to be done when errors arise. Try it out for yourself:

go run main.go -url http://google.com

Let’s assume for the sake of argument that these are the final project specs. We can now print the contents of any valid web page a user enters and (not-so-gracefully) handle errors. What happens here when we want to write some tests for our application?

Well, since http.Client is going to make real requests, our test would have to rely on a valid internet connection. In this case it might not be the end of the world because most CI machines would have this ability, but what if it were a database connection? A connection to a queue?

The short of it is that we need to be able to mock these connections and our code is not currently mockable. Enter phase 3.

Phase 3: “Dependency injection is your friend”

Suppose in our send function we were to accept an http.Client as a parameter. Would this help us test our code? Clearly it would not because as we stated before, an http.Client will make a real request. So how do we allow both a real http.Client and a mock? Interfaces to the rescue!

Interfaces in Go are satisfied via composition meaning that they are implicitly satisfied by any type that matches the signature of that interface. Let’s see what an interface would look like for our use of http.Client:

type HttpClient interface {
Get(string) (*http.Response, error)
}

Because the only method of http.Client we are currently using is Get, the http.Client will implicitly satisfy our HttpClient interface! So how do we use this new interface? In our main function, we can wire up our dependencies and inject them into the functions that need them.

package main

import (
"fmt"
"net/http"
"io/ioutil"
"flag"
)

var (
url string
)

type HttpClient interface {
Get(string) (*http.Response, error)
}

func init() {
flag.StringVar(&url, "url", "http://google.com", "Which URL do we want to parse?")
flag.Parse()
}

func main() {
client := &http.Client{}
err := send(client, url)

if err != nil {
panic(err)
}
}

func send(client HttpClient, link string) error {
response, err := client.Get(link)

//... truncated for brevity
}

At this point we have a function which receives it’s HttpClient dependency and uses it to print the body of an *http.Response. Now we’re at a place where we can test this bad boy! Let’s make a file and name it main_test.go:

package main

import (
"bytes"
"io/ioutil"
"net/http"
"testing"
)

type MockHttpClient struct {}

func (m *MockHttpClient) Get(url string) (*http.Response, error) {
response := &http.Response{
Body: ioutil.NopCloser(bytes.NewBuffer([]byte("Test Response"))),
}

return response, nil
}

func TestSendWithValidResponse(t *testing.T) {
httpClient := &MockHttpClient{}
err := send(httpClient, "IT_JUST_WORKS!")

if err != nil {
t.Errorf("Shouldn't have received an error with a valid MockHttpClient, got %s", err)
}
}

Let’s break this down one concept at a time. First we have our mock.

type MockHttpClient struct {}

Since our MockHttpClient doesn’t do anything but return a static response we can start with an empty struct.

func (m *MockHttpClient) Get(url string) (*http.Response, error) {
response := &http.Response{
Body: ioutil.NopCloser(bytes.NewBuffer([]byte("Test Response"))),
}

return response, nil
}

Remember our interface definition for HttpClient? It has one method called Get that accepts a URL and returns an *http.Response and an error. Here we are writing the Get method so that our MockHttpClient will satisfy the HttpClient interface. In the body of Get we are simply creating a new response object and initializing it with a Body property. Because the body needs to satisfy the io.ReadCloser interface, we used ioutil’s NopCloser which has both a Read and a Close method. Finally we return our mocked response object and a nil error.

func TestSendWithValidResponse(t *testing.T) {
httpClient := &MockHttpClient{}
err := send(httpClient, "IT_JUST_WORKS!")

if err != nil {
t.Errorf("Shouldn't have received an error with a valid MockHttpClient, got %s", err)
}
}

In this simple test case we initialize a MockHttpClient and pass it to our send function with any URL as a parameter. Since we created the MockHttpClient ourselves, we know that it will return a valid response and it should not return an error. We assert that we should not receive an error, and if we do, something is wrong! Run the test for yourself:

go test -v

You’ll get a nice little test result that indicates your tests are PASSING!

=== RUN   TestSendWithValidResponse
Test Response
--- PASS: TestSendWithValidResponse (0.00s)
PASS
ok _ 0.008s

You’ll also notice that “Test Response” was printed to the screen while running your test because the send function is still printing the body of the response. While this principle can be extrapolated further, this is an excellent starting point for your next Go application.

TL;DR: Inject your dependencies to functions and structs as interfaces so they can be easily swapped for different implementations (including mocks in your tests).

Disclaimer: I wanted to add a Phase 4: “Strut your struct” but omitted to keep the post manageable for people new to dependency injection and Go. Will include in an edit or a possible future article.