Using Go Interfaces for Testable Code

Gabe Szczepanek
The Startup
Published in
4 min readAug 22, 2019
Photo by Mirza Babic on Unsplash

Interfaces are abstractions that define the behavior of a particular type but do not specify the details of how that behavior is implemented. If you think of a bank teller, you understand that you can request and receive money from them (provided you have the money in your bank account of course). Whether the teller is an employee at a bank branch or the ATM at your local deli the outcome defined by the “teller” interface remains the same. You can still request and receive money even when the particular mechanics differ between the person and the machine implementations.

Many object-oriented programming languages have some notion of interfaces. Go’s interfaces are particularly interesting and distinctive because they are satisfied implicitly. This means that instead of specifying that the object you are creating implements a particular interface, the Go compiler figures that out for you. If your object contains all the methods defined by an interface Go understands that your object implements that interface and can be used wherever that interface is used.

So why is that helpful and how can we use interfaces to write testable code?

Case: Using External Libraries

A common scenario that illustrates the utility of interfaces is testing code which uses an external client library to do something; in this case to get data from an imaginary web API.

Imagine we have an external package that is a library written to interact with an external web service. It would likely export a Client object with methods to interact with the API. Imagine a GetData method which in this case can only return data but could make a web request and theoretically return an error if this was a real implementation.

Here we have package foo which is the code we are writing that wishes to use the Client to get data from the external API and then do things with it. We have two possible error cases in this implementation — one in which GetData returns an error, perhaps because the web request failed, and another in which the data returned was not what we expected and we therefore can not process it. Both paths will result in our Controller function returning an error.

Now let’s take a look at how we would test the Controller function. We could have two basic tests, one which tests the success of the function and one which tests the two failure cases. The problem is that we cannot influence the behavior of the external API and therefore cannot force GetData to succeed or fail.

The above test TestController_Success will pass but TestController_Failure will not because of our inability to test the failure cases. This is confirmed and illustrated in the coverage report.

Not only are our failure cases uncovered but our unit tests are now non-deterministic, at the mercy of the external API to fail or succeed at will. We need a way to stub the behavior of GetData in our code so that we can manipulate its output during our unit tests and that is where interfaces become very useful.

Using Interfaces To Enable Stubbing

If we can define an interface that the external library’s Client object satisfies, then we can use that interface in our code instead. This would allow us to feed in a fake client object during testing and the real one during normal operation.

In other languages this would require us to modify the external library code to explicitly state that Client implemented our new interface (oh no!), but since Go interfaces are satisfied implicitly the compiler will already know that! Yay!

So here we define the IExternalClient interface (Go purists please don’t crucify my naming) that specifies the methods that we use in our foo Controller and we modify the controller function to take an IExternalClient interface type as a parameter. The rest of the controller function then operates as it was before, calling the GetData method of the interface rather than the specific external Client implementation.

Now let’s take a look at how much easier it is to test our Controller method. We can implement the IExternalClient interface by simply implementing the GetData method on our MockClient object, but in our MockClient implementation we have it return what we want it to return. We then feed in our implementations of IExternalClient to Controller in our tests. We can use MockClient to return different values for the result of GetData and our FailingClient to have GetData return an error.

We are now easily able to handle all the branches of our Controller function as the updated coverage report confirms.

As you can see, writing and using interfaces can increase the flexibility and testability of your code, and can be an easy solution for stubbing external dependencies.

The Go standard libraries use interfaces heavily and you can find great examples in packages like io and net/http.

Happy Coding!

--

--