Introduction
Do you get annoyed when others (or you) forget to run the code generation command and now nothing works/code is out-dated?
Compile Time Assertions to the rescue!
Scenario
Suppose you have an interface:
type MyInterface interface {
WithMethod(method string) MyInterface
}
This interface has an associated implementation:
type MyInterfaceImpl struct {
privateField string
}
func (my *MyInterfaceImpl) MyInterface {
// ... implementation code
}
Which you’ve have been auto-generating mock objects for using the awesome mockery package.
Why Though
You are doing this because you want users of your library to be able to mock your library.
- Thus allowing them to isolate their own code when writing unit tests.
- Meaning we don’t actually use this mock in OUR tests, it is used by OTHERS
So the interface definition above is augmented with:
// Generate mocks by running "go generate ./..."
// go:generate mockery --name MyInteface
type MyInterface interface {
WithMethod(method string) MyInterface
}
Which produces when go generate ./...
is ran:
mocks/MyInterface.go
// MyInterface is an autogenerated mock type for the MyInterface type
type MyInterface struct {
mock.Mock
}
// WithMethod provides a mock function with given fields: method
func (_m *MyInterface) WithMethod(method string) (pkg.MyInterface) {
// ... some mock implementation code
Problem
Now what happens when Greg who isn’t aware of the auto-generated code, adds another method to MyInterface
and MyInterfaceImpl
:
type MyInterface interface {
WithMethod(method string) MyInterface
WithAnotherMethod(method string) MyInterface // New
}
Since the MyInterface
has changed but Greg didn’t auto-generate the mocks using go generate ./...
, the mock object will not fulfill the interface and cannot be used for testing by downstream users (Thanks Greg)!
Solution
Add a compile-assertion above the interface definition after you have initially generated the mock object (Also add –inpackage flag to go generate command to avoid import cycles).
// Generate mocks by running "go generate ./..."
// go:generate mockery --name MyInteface --inpackage
var _ MyInterface = &MockMyInterface{} // COMPILE-TIME Assertion
type MyInterface interface {
WithMethod(method string) MyInterface
}
Now if Greg adds another method but forgets to run go generate
var _ MyInterface = &MockMyInterface{}
type MyInterface interface {
WithMethod(method string) MyInterface
WithAnotherMethod(method string) MyInterface // New
}
He will get hit with:
cannot use &MockMyInterface literal (type *MockMyInterface) as type MyInterface in assignment:
*MockMyInterface does not implement MyInterface (missing WithAnotherMethod method)
At which point, he might realize that he needs to add the method to the mock object using go generate
.