Stubbing Out Interfaces in Go

Jason
Stupid Gopher Tricks
3 min readJul 16, 2015

Here’s a situation in which you may find yourself. You have a service that exposes a lot of functionality. Because you’re a good software engineer, you want to define an interface so you can stub out some of those calls in your tests.

type doesEverything interface {
countChickens() int
hatch()
checkWeather(lat, lng float64) string
checkScores(home, away team) (int, int)
checkYourself() bool
wreckYourself()
}
func countChickensAndHatch(de doesEverything) error {
c := de.countChickens()
if c > 10 {
de.hatch(c)
return nil
}
return errors.New("not enough chickens!")
}

And your test code:

// TestCountChickensAndHatch tests that various inputs to
// countChickensAndHatch produce expected errors.
func TestCountChickensAndHatch(t *testing.T) {
cases := []struct {
numChickens int
exp error
}{
{1, errNotEnoughChickens},
{10, errNotEnoughChickens},
{11, nil},
{1000, nil},
}
for _, c := range cases {
s := stub{numChickens: c.numChickens}
got := countChickensAndHatch(s)
if got != c.exp {
t.Errorf("countChickensAndHatch(%d); got %v, want %v", got, c.exp)
}
}
}
type stub struct {
numChickens int
}
func (s stub) countChickens() int { return s.numChickens }

Obviously this is a pretty contrived example — this interface is terrible — and that’s a pretty meaningless test. That’s not what I want to demonstrate. This test also doesn’t compile:

cannot use stub literal (type stub) as type doesEverything in argument to countChickensAndHatch:
stub does not implement doesEverything (missing checkWeather method)

Yep. Our stub implementation doesn’t implement all the necessary methods, namely checkWeather.

A lot of people would just start throwing in all the necessary methods into the stub implementation to get it to compile:

func (s stub) checkWeather(float64, float64) string { return "" }
func (s stub) checkScores(team, team) (int, int) { return 0, 0}
func (s stub) checkYourself() bool { return true }
func (s stub) wreckYourself() { panic("wrecked") }
func (s stub) hatch(int) {}

And there we go, we’ve satisfied the interface, we’re compiling, and we’re off to the races.

But what if the interface adds another method? What if it adds a dozen more? You don’t want to have to update your stub for each and every one of them, do you? Do you?!

Of course not, you’re better than that. You know the trick!

Instead of writing a bunch of nonsense stub methods, we can just tell the Go compiler that the stub implements the doesEverything interface, by embedding the interface in the struct:

type stub struct {
doesEverything
numChickens int
}
// Define the stub implementation of countChickens, as before
func (s stub) countChickens() int { return s.numChickens }

That’s enough to tell the Go compiler that stub has all the methods that the interface defines, without having to write all those methods out yourself. If new methods are added to the interface, the stub will get them too.

But beware! Lest you think this is a fancy tricky way to ensure that a struct satisfies an interface and use this trick in production: even though stub has those methods, they will panic if called:

panic: runtime error: invalid memory address or nil pointer dereference

So in the above test, since countChickensAndHatch calls hatch, we’ll also need to stub out that method as well:

func (s stub) hatch(i int) { fmt.Printf("hatching %d...\n", i) }

But at least we don’t have to stub out each and every method on the interface.

Because unimplemented methods panic, this technique is not meant to be used outside of test code, where you can ensure that no code path will ever end up calling an unimplemented method. And if your tests panic, that’s a heck of a lot less serious than your production code panicking.

--

--