In many programming languages, interfaces are the secret sauce to writing testable and mockable code. In C#, C++, Java, and many others, a type implements an interface if the definition of the type explicitly specifies that relationship. Example in Java:
interface AnInterface {
public void AMethod()
}
interface AnotherInterface {
public void AnotherMethod()
}
class InterfaceImplementation implements AnInterface {
public void AMethod() { ... }
public void AnotherMethod() { ... }
}
In the code above, InterfaceImplementation
implements AnInterface
, but not AnotherInterface
.
Go does things differently: types implement interfaces implicitly. There is no explicit declaration of intent (e.g. the implements
keyword). As long as a type implements all the methods in an interface (with matching signatures), it implements the interface. Example:
type AnInterface interface {
AMethod()
}
type InterfaceImplementation struct {}
func (t InterfaceImplementation) AMethod() {...}
In the snipper above, InterfaceImplementation
implements AnInterface
because it declares all the methods in the interface.
Why implicit interface implementation is my favorite feature of Go
A few weeks ago, I wrote some code that reads a file from S3 and does something with it. It uses the AWS S3 SDK for Go. The code was similar to this:
func ProcessFile(s3Client s3.Client, bucket string, fileName string) error {
result, err := s3Client.GetObject(&s3.GetObjectInput{
Bucket: &bucket,
Key: &fileName,
})
// ...
}
That code is somehow testable: you can build and pass in a s3.S3 object, but you need to create one in such a way that calling GetObject
does the right thing. As far as I can tell, there’s no way to do it without reaching an actual S3 server. Ideally, I would like my tests to not do any IO operations.
In other programming languages, if a library doesn’t expose an interface for one of it’s public types, testing code that depends on it is a pain. You can either not test it (please don’t do that!), or you can write a wrapper around it (e.g. through an Adapter, Decorator, or Facade). Even when you have an interface, you can end up with an interface that has dozens of methods (see the Amazon S3 client for Java) and you have to implement all of them even if you don’t need them. You can write a wrapper in Go and it would work, but there’s an easier way. You can probably tell where this is going…
The code only depends on the GetObject
method in the s3.S3
type and, as I mentioned before, interfaces are implemented implicitly in Go. If you write your interface that has the GetObject
method with the same signature as the one in the S3
type, the S3
type will satisfy it. Let’s do that:
type S3Reader interface {
// Has the same signature as s3.S3.GetObject
GetObject(input *s3.GetObjectInput) (*s3.GetObjectOutput, error)
}
Then update the code to use the interface instead of the S3
type:
func ProcessFile(s3Client S3Reader, bucket string, fileName string) error {
result, err := s3Client.GetObject(&s3.GetObjectInput{
Bucket: &bucket,
Key: &fileName,
})
// ...
}
ProcessFile
can be invoked with the actual s3.S3
object as argument or any other object that implements the S3Reader
interface, such as a mock.
Mocking
Talking about mocks, I like to use a particular pattern when creating interface mocks. I don’t use a mocking framework. I like to create a struct with each method of the interface being a function pointer. That way, I can reuse the mock and only implement those functions that I care about:
type S3ReaderMock struct {
GetObjectFunc func(input *s3.GetObjectInput) (*s3.GetObjectOutput, error)
}
// This method has the same name and signature as
// GetObject in S3Reader, so S3Reader is implemented implicitly by S3ReaderMock
func (mock *S3ReaderMock) GetObject(input *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
return mock.GetObjectFunc(input)
}
And I use it as:
mockClient := S3ReaderMock{
GetObjectFunc: func(input *s3.GetObjectInput) (*s3.GetObjectOutput, error) {
// Implement the mock here
},
}
ProcessFile(&mockClient, "my-bucket", "my-file")
Final thoughts
In Go, a type implements an interface if it implements all the methods in an interface. The name of the methods and the type of arguments, must match exactly. It is possible to end up with types that implement interfaces that you don’t expect when the interfaces are small and the methods have common names.
Even if a package doesn’t expose interfaces for its public types, anyone can create their own. If you are a package author or you’re writing code that others use, there’s no to need create interfaces just for mocking purposes.
Try it yourself
A fully working example of the code is available on the Go Playground.