Interfaces in Go
Tags: go, softwaredesign, testing, dependencyinjection
Published: 2018-06-12 03:35:19

---
title: Interfaces in Go
published: true
description: Beyond vehicles, animals and shapes. This post is a practical introduction to one use of Go's interfaces.
tags: go, software design, testing, dependency injection
cover_image: https://thepracticaldev.s3.amazonaws.com/i/ffw5uah71ho4dmpbrtp1.JPG
---

Interfaces in Go are really great. They help us get good test coverage and make our code's dependencies explicit and therefore easier to read.

Let's look at some existing code and see how we can change it to make the dependencies explicit.

The important thing to focus on here is that this function takes no parameters but calls out to imported functions such as `glog`, `http` and `netutil`.

```go
// Check validates http connectivity type, direct or via proxy.
func Check() (warnings, errors []error) {
	glog.V(1).Infof("validating if the connectivity type is via proxy or direct")
	u := (&url.URL{Scheme: hst.Proto, Host: hst.Host}).String()

	req, err := http.NewRequest("GET", u, nil)
	if err != nil {
		return nil, []error{err}
	}

	proxy, err := netutil.SetOldTransportDefaults(&http.Transport{}).Proxy(req)
	if err != nil {
		return nil, []error{err}
	}
	if proxy != nil {
		return []error{fmt.Errorf("Connection to %q uses proxy %q. If that is not intended, adjust your proxy settings", u, proxy)}, nil
	}
	return nil, nil
}
```
([source](https://github.com/kubernetes/kubernetes/blob/d9506299ac8bc163e2e3f5131387f226fcbc64d5/cmd/kubeadm/app/preflight/checks.go#L443))

Let's look at the first line of the function: `glog.V(1).Infof(...)`. All we really care about here is that we can call `Infof`. We can define a type that exactly satisfies that property:

```go
type logger interface {
    Infof(string, ...interface{})
}
```

Now we can have our function depend on just that interface.

```go
func Check(log logger) (warnings, errors []error) { ... }
```

And we can replace the call to `glog.V(1).Infof` with `log.Infof`.

```go
type logger interface {
    Infof(string, ...interface{})
}

func Check(log logger) (warnings, errors []error) {
	log.Infof("validating if the connectivity type is via proxy or direct")
        ...
}

func main() {
    // glog.V(1) is now merely an implementation detail. This function accepts anything that implements our interface!
    Check(glog.V(1))
}
```

The process we just went through is this:

1. Look at the dependency we want to be able to control.
2. Extract the behavior into an interface.
3. Pass that interface into the function.
4. Replace the function call with a method call to the interface passed in.
5. Pass an implementation of the interface we defined into the function call.

We can repeat this for any of the other implicit dependencies we want. Here is a more complex refactor that wraps `http.NewRequest` in a struct that implements an interface we create that exactly defines the behavior we need.

```go
type requestBuilder interface {
    NewRequest(string, string, io.Reader) (*http.Request, error)
}

type reqBuilder struct {}
func (rb *reqBuilder) NewRequest(method, url string, body io.Reader) (*http.Request, error) {
    return http.NewRequest(method, url, body)
}


func Check(rb requestBuilder) (warnings, errors []error) {
    ...
    req, err := rb.NewRequest("GET", u, nil)
    if err != nil {
        return nil, []error{err}
    }
    ...
}

func main() {
    Check(&reqBuilder{})
}
```

Why would we go through this much trouble? Well, in this case we really wouldn't, but it all depends on the situation. If the error case were very complex and we had a need to test specific errors that are returned from `NewRequest`, then this makes sense to me. But if you just care about testing `err != nil`, it's a lot easier to read the [`http.NewRequest` source][src] and find out how to generate an error from that function. 

[src]: https://golang.org/src/net/http/request.go?s=25278:25347#L759

The last dependency, `netutil`, is left as an exercise to the reader.

One other very important thing to note is that the interfaces are defined at the call site. They are not imported. We don't import interfaces because it is very likely the interface we import would be larger than what we actually need. Our interfaces should be exactly the behavior we seek and nothing more.