My problem isn't terribly complex. I'll write a simple example first, and then show the real code after in case I missed any subtleties.
So I have a third-party library:
// Third party library
type ThirdPartyFoo struct {}
func (t *ThirdPartyFoo) DoFoo () *ThirdPartyFoo {
println("I'm a third party library!")
return t
}
And I want to use that library to invoke DoFoo()
:
type MyThing struct {
TheFoo ThirdPartyFoo
}
func (myThing *MyThing) WhichLib () {
myThing.TheFoo.DoFoo()
}
And I want to test it with a mocked version of ThirdPartyFoo
because, how else am I supposed to verify that DoFoo()
was A) invoked, B) invoked with the correct parameters, and C) MyThing
responds correctly based on different return values?
I tried using an interface inside MyThing
instead of directly depending on ThirdPartyFoo
...
type IThirdPartyFoo interface {
DoFoo() *IThirdPartyFoo
}
type MyThingWithI struct {
TheFoo IThirdPartyFoo
}
func (myThing *MyThingWithI) WhichLib () {
myThing.TheFoo.DoFoo()
}
And then invoking the function...
func main() {
realFoo := ThirdPartyFoo{}
realThing := MyThingWithI{
TheFoo: &realFoo,
}
realThing.WhichLib()
}
But it fails at runtime:
proxyutils/serviceregistrar.go:44:9: cannot use &realFoo (type *ThirdPartyFoo) as type IThirdPartyFoo in field value:
*ThirdPartyFoo does not implement IThirdPartyFoo (wrong type for DoFoo method)
have DoFoo() *ThirdPartyFoo
want DoFoo() IThirdPartyFoo
So... I've spent a couple hours trying to figure this out, and another hour writing up this post... and I'm still lost. The only solution I can come up with is to completely wrap the third party library, like so:
type IThirdPartyFooWrapper interface {
DoFoo() IThirdPartyFooWrapper
}
type ThirdPartyFooWrapper struct {
IThirdPartyFooWrapper
ThirdPartyFoo ThirdPartyFoo
}
func (w *ThirdPartyFooWrapper) DoFoo () IThirdPartyFooWrapper {
w.ThirdPartyFoo.DoFoo()
return w
}
type MyThingWithI struct {
TheFooWrapper IThirdPartyFooWrapper
}
func (myThing *MyThingWithI) WhichLib() {
myThing.TheFooWrapper.DoFoo()
}
But there are SO MANY levels of indirection already that I really hate to add this one. This is what mocking was made for, but it seems the Go language has completely screwed over any poor schmuck unfortunate enough to have started using a third-party library that isn't making use of interfaces.
And finally, here's my real code. It's the *mux.Router
as part of ServiceRegistrar
that is being problematic.
package proxyutils
import (
"errors"
"github.com/gorilla/mux"
"net/http"
)
type IProxyRegistrar interface {
Register(input Registration) error
}
type ServiceRegistrarFactory struct{}
func (_ *ServiceRegistrarFactory) Build(proxyRegistrar IProxyRegistrar, router *mux.Router) (*ServiceRegistrar, error) {
if nil == proxyRegistrar {
return nil, errors.New("cannot create ServiceRegistrar with nil IProxyRegistrar")
}
if nil == router {
return nil, errors.New("cannot create ServiceRegistrar with nil mux.Router")
}
return &ServiceRegistrar{
proxyRegistrar: proxyRegistrar,
router: router,
}, nil
}
type ServiceRegistrar struct {
proxyRegistrar IProxyRegistrar
router *mux.Router
}
func (r *ServiceRegistrar) Register(userRegistration Registration, f func(http.ResponseWriter, *http.Request)) error {
err := r.proxyRegistrar.Register(userRegistration)
if nil == err {
var route *mux.Route
if userRegistration.PathIsPrefix {
route = r.router.PathPrefix(userRegistration.Path)
} else {
route = r.router.Path(userRegistration.Path)
}
route.Methods(userRegistration.Methods...).HandlerFunc(f)
return nil
} else {
return err
}
}