Testing in Go

Making your Go service testable requires dedication from the very first minute of its existence. To turn an old service testable, while is not impossible, is a harder task. So be sure to have testing in your mind (always, not just in Go) before you start coding.

gophers with tools
gophers with tools

Interfaces everywhere

Testing usually comes with mocking holding its hand. Mocking functions, services and stuff in Go relies heavily on interfaces. We’ll talk about tools later on but at least the two most used rely on implementing the interface you want to test and do checking on it’s parameters and return values.

So, the rule of thumb here is

Make an interface the front door of your structs

interfaces, interfaces everywhere
interfaces, interfaces everywhere

Expose every logic via an interface. The smaller the better, we like the I in SOLID.

This allows us to expose always what we just need, not everything, and make our code mockable, essential for keeping our tests independent on each other.

Mocking tools

Now that we have our interfaces ready to be mocked we can start writing those mocks. The only problem is, we are developers, lazy developers. We like it when there is a tool simple enough to enable us to code faster. Writing mocks in Go suites this scenario.

There are a couple of tools to assist you on the quest of mocking. Their goal is essentially the same so I’d recommend you to read some of them and choose the one that you feel most comfortable with.

The two most common are

As of writing this article I’ve had more experience with testify + mockery but gomock is the official mock tool and I’m willing to give it a try on my next project.

Both tools have great readmes explaining how they work. In essence, they generate mocks that implement your interfaces and you can use to tell which parameters they are expected to receive and what to return in each case

// gomock examplefunc TestFoo(t *testing.T) {
ctrl := gomock.NewController(t)

// Assert that Bar() is invoked.
defer ctrl.Finish()

m := NewMockFoo(ctrl)

// Asserts that the first and only call to Bar() is passed 99.
// Anything else will fail.

// testify examplefunc TestSomething(t *testing.T) {

// create an instance of our test object
testObj := new(MyMockedObject)
// setup expectations
testObj.On("DoSomething", 123).Return(true)

// call the code we are testing

// assert that the expectations were met

Using your mocks

So, we now have our functions ready to be mocked by the use of interfaces. We have our mocks generated and know how to setup expectations. How de we actually use them inside our tests?

When we create a struct we pass its parameters as arguments and then do something like


To successfully use our mocks we need that Dependency to be our mocked interface. How do we do this? Simply when creating your struct pass the mocked object where the dependency parameter is expected

type Mockable interface {
DoSomething(uint) bool
type mockableImpl struct {}// Implement our interface
func (m mockableImpl) DoSomething(u uint) bool {
return u > 0
type myStruct struct {
Dependency Mockable
func (m myStruct) Do(u uint) bool {
return m.Dependency.DoSomething(u)
func TestSomething(t *testing.T) {
// create an instance of our mock object
mockedObj := new(MyMockedObject)
// setup expectations
mockedObj.On("DoSomething", 123).Return(true)

// call the code we are testing
aStruct := myStruct{Dependency: mockedObj}
result := aStruct.Do(123)
// assert expectations
assert.True(t, result)

There is no magic trick here, we are just using the power of interfaces.

Another way of achieving the same result, but that I prefer, is creating a Constructor function that depends on interfaces and returns a struct. This way we provide a flexible way of creating structs that implement and use many interfaces

func NewMyStruct(dependency Mockable) *myStruct {
return &myStruct{Dependency: dependency}

By using this function we have one way of creating a struct that may implement many interfaces and can be used in many places of our codebase.

Software engineer with 5+ years working for startups and some of LATAM’s unicorns. Passionate about high quality, resilient software with focus on product dev.