I am having a hard time figuring out an idiomatic way of writing testable code in golang. I understand the importance of interfaces and their use in testing, but I haven't figured out how to mock/test external struct dependencies.
As an example, I have written the following which simulates a wrapper for creating a pull request on GitHub.
type GitHubService interface {
}
type gitHubService struct {
CreatePullRequest(...) (PullRequest,error)
}
func (s gitHubService) CreatePullRequest(...) (PullRequest,error) {
tp := github.BasicAuthTransport{
Username: strings.TrimSpace(/*.....*/),
Password: strings.TrimSpace(/*.....*/),
}
client := github.NewClient(tp.Client())
pr,err := client.Repositories.CreatePullRequest(...)
...
}
func TestPullRequest(t *testing.T) {
service := gitHubService{}
pr,err := service.CreatePullRequest(...)
...
}
If I was writing a unit test for GitHubService.CreatePullRequest(...)
I would want to mock the call to client.Repositories.CreatePullRequest(...)
and probably even github.NewClient(...)
to return mock implementations that I can control.
With tools such as gomock
it seems that you are out of luck with structs and package functions.
What is the idiomatic way to handle this? I am very familiary with Inversion of Control and the different patterns such as Dependency Injection and Service Locator, but I have heard countless times that this is not idiomatic.
One important design feature of Go is decoupling (Watch this great talk from Bill Kennedy about that topic). Inside your method there are some dependencies, which could be decoupled. This coupled method makes it not really testable.
Thing you should refactor:
tp := github.BasicAuthTransport
: you should not initialize the authorization inside of your method. It should move into your gitHubService as a parameter. Inside your method call you can access ist via s.tp
. You could also make it an input parameter of the method.github.NewClient()
and client.Repositories.CreatePullRequest(...)
just read about the golang best practices from Peter Bourgon Make dependencies explicit!. The alternative is to create an interface, which contains all the called functions. This interface should be an input to your method.After your code is decoupled you can mock everything very easy. If you use interfaces as an input you can just create a mock struct, which implements the interface. If you make the dependencies explicit you can overwrite them. In the last case the code for storing the values of the calls is not so clean, but it also works. The idiomatic go way is to use interfaces.
I had a similar problem and looks like, the only option you have is to have another layer in between which would call your "unmockable" clients.
For e.g. for mocking the govmi clients (vmware clients sdk for golang), I had to have a "myCustomClient" having interfaces and structs to make calls to govmi.Client.AnyMethod..
I could then generate mocks for "myCustomClient".
mockgen -source myCustomClient.go -package myPackage -destination myCustomClientMock.go
You can install it by: got get github.com/golang/mock
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With