
When writing tests for your code which make use of interfaces, Golang can be quite fiddly to work with. You may only wish to mock one method but given the nature of interfaces you'll often implement them all to satisfy the compiler. What if there were a way to create partial interfaces Golang would accept?
What is a Go interface?
At its core, a Go interface is simply a collection of method signatures. It defines a set of methods that a type must implement in order to be considered an instance of that interface. For example, consider the following interface definition:
type UserDatabase interface {
GetUser() string
InsertUser() error
}
This defines an interface called UserDatabase
with two method signatures GetUser
and InsertUser
. Any type that has a method with these exact signature is considered to be an implementation of the UserDatabase interface.
How do interfaces work in Go?
In Go, a type is said to implement an interface if it defines all the methods in the interface. This is determined at compile-time, which means that any code that uses an interface can be sure that the methods defined in the interface will be available at runtime.
For example, let's say we have a type UserStore
that implements the UserDatabase
interface:
type UserStore struct {}
func (m *UserStore) GetUser() string {
return "Mark"
}
Because UserStore
has a method with the signature GetUser()
, it is considered to be an implementation of the UserDatabase
interface. We can then use a value of type UserStore
wherever a GetUser
is expected:
func UserDoSomething(u UserDatabase) {
u.GetUser()
}
func main() {
var u UserStore
UserDoSomething(u)
}
In this example, we define a function called UserDoSomething
that takes a value of type UserDatabase
. We then create an instance of UserStore
and pass it to UserDoSomething
. Because UserStore
implements the UserDatabase
interface, it can be used as an argument to UserDoSomething
.
This is the power of Go interfaces - they allow for code to be written in a way that is independent of the specific types being used. By defining interfaces that describe the behavior that is required, we can write functions and other code that can work with any type that satisfies those requirements.
Partially implementing an interface in Golang
So you've been tasked with writing tests for a method which implements an interface, and you want to stub the output of that method without implementing the entire interface. How do you do that, and why would you want to do that?
Let's say your website makes use of an interface for it's user store/database as we've been discussing in the above examples. You might not wish to spin up a test database, but instead mock the output of a GetUser()
method which implements the UserDatabase
interface. In this case, you can do the following:
type UserDatabase interface {
GetUser() string
InsertUser() error
}
type UserStore struct {
// Database sql.Database
// Other bits you might want in a UserStore object
}
func NewUserStore() *UserStore {
return &UserStore{}
}
func (m *UserStore) GetUser() string {
// sql.Database.Execute().....
return "Mark"
}
func (m *UserStore) InsertUser() error {
return nil
}
// Embed and promote the UserStore methods to MockUserDatabase
type MockUserDatabase struct {
UserStore
MockUser string
}
func (m *MockUserDatabase) GetUser() string {
return m.MockUser
}
func main() {
// In your actual code
u := NewUserStore()
fmt.Println(u.GetUser())
// In your tests
m := MockUserDatabase{
MockUser: "Not Mark!",
}
fmt.Println(m.GetUser())
}
// Code Output: Mark
// Test Output: Not Mark!
Have a play in the Go Playground.
In the above code we define an interface called UserDatabase
that specifies two methods: GetUser()
that returns a string and InsertUser()
that returns an error
. We also have a method called NewUserStore()
which returns a UserStore
struct which is used by GetUser()
and InsertUser()
.
We then have the MockUserDatabase
struct which embeds and promotes the UserStore
methods from the UserDatabase
interface, however here we are partially defining the interface with just the GetUser
method. As you can see, we're returning the mocked value of m.MockUser
as the response of this method, completely bypassing any of the database logic you may have had to deal with in your testing. As a result, we have two strings which are printed: One which prints Mark
, and one which prints Not Mark!
which we've "injected" via a partially implemented interface mock. Now when writing tests, you'll be able to use a for loop to iterate over all your test cases and make uses of these new mocked values to test against different criteria.
In summary
By making use of partial interfaces Golang allows you to easily mock what would otherwise be complicated methods which might call out to databases, RPCs, and external services. You've now greatly simplified your testing strategy, and cut back the lines of code required to run valid and complex integration tests. Interfaces are a fairly straight forward concept, however Go's implicit use of them can make for a bit of a confusing time. Hopefully armed with this knowledge, you'll be able to tackle your testing problems with great ease.