Mocking is a useful testing strategy but the type system in Go can get in the way. In this post I briefly discuss an easy to follow pattern to ease mocking when writing tests.
There are various tools to ease writing test cases using Mocking. mockery is a very popular choice, that works by inspecting an existing interface and generating code that implements mocks for said interfaces.
This approach can be quite powerful, but the generated code implementing the mocking interface becomes an additional asset that needs to be managed. Depending on your specific situation, this might be overkill.
For a recent project I needed to mock a third party Go library providing bindings into a C library. This caused issues due to the underlying implementation which relied strongly in the use of user supplied callbacks that invoke class methods to cause things to happen.
While the details of the specific libraries are not too important, we should keep in mind that the implementation pattern in this scenario was not common in the Go world. Because of this, mockery was unable to handle this case well because there wasn’t really a Go interface
I could mock to test the functionality I needed.
Write testable code…
This is an often repeated mantra. You should be thinking in how to test your code while you write it, so that your job becomes easier. In my earlier cuts, my code looked like this:
func Widget(p ...interface{}) {
// code, code, code
externalClib.SomeMethod(params)
// code, code, code
}
So, the call to externalClib.SomeMethod()
would cause state changes that would affect other parts of my code. I needed to ensure that the call to Widget()
would end up calling externalClib.SomeMethod()
as appropriate, with the right parameters for each case.
All in all, your typical test case. Unfortunately, there’s no real way in Go to impersonate externalClib.SomeMethod
. However, a simple change makes this task trivial. Consider the following snippet, depicting the final code I ended up writing:
// SomeMethod is a func pointer to externalClib.SomeMethod which
// can be overriden at any point for testing
var someMethod = externalClib.SomeMethod
func Widget(p ...interface{}) {
// code, code, code
someMethod(params)
// code, code, code
}
As you can see, the call to externalClib.SomeMethod
is made through someMethod
, a package variable that can easily be replaced in the test suite, as follows:
func TestWidget(t *testing.T) {
calledSomeMethod = false
someMethod = func(params []string) {
calledSomeMethod = true
if params[0] != "cool thing" {
t.Error("thing is not cool")
}
}
Widget()
if !calledSomeMethod {
t.Error("someMethod should have been called")
}
}
This pattern served me as the basis for writing tests that exercised my code and verified that the calls to third party code would use the expected parameters.