Using Go Interfaces for Testable Code

Gabe Szczepanek
Aug 22, 2019 · 4 min read
Image for post
Image for post
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

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.

Image for post
Image for post

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

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.

Image for post
Image for post

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!

The Startup

Medium's largest active publication, followed by +707K people. Follow to join our community.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch

Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore

Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store