After a week or two of hacking with Go for just a few short hours, I can unequivocally say I love it. I resisted learning Go because I thought static typing would make me less productive. I love(d) monkey patching with Ruby! There is (was) so much freedom that to get something running quickly all I needed was to see “OK” after running ruby -c. Little did I know the mindspace occupied by that nagging worry in the back of my head saying “remember to write unit tests!” And, “I can almost certainly be sure that all my variables are spelled correctly.” Turns out there was a huge cost to be paid for this freedom, because my code was often buggy, and I never got around to 100% test coverage. Static type checking removes that issue for me and now that I have aligned my brain around struggling a little bit more during compilation, my code just works once I am past that phase. With Ruby all the problems were in the runtime phase, my problems are rarely there with Go.

Testing is well integrated into the go build process, but because there is no monkey patching, testing takes a little more work. The first project I decided to do with go was docker-search, a tool to search docker images better than just keywords in the docker index, and I needed to mock out the http client calls I was making to the docker index and registry servers (to retrieve the full Dockerfiles). There are a lot of examples out there for mocking out server side http calls, but not so many about client side calls. Fortunately, it turned out to be trivial. I refactored my client.go library so that I could pass in an interface with a single Get() method. Then, inside the initialization of the client within the actual tool I pass in a real http client, and inside my tests I pass in a fake client.

Interfaces in Go were slightly different than I expected, because I initially assumed an interface would need the same exact signature down to the receiver “class” but in fact Go does not have classes. This means that if you define a method like func (a* A) Get( url string ) string that it can be used in the same place as func (b* B ) Get( url string ) string. So, to implement my interface, I just define a two structs, one inside the tool which then defines the method with a real http client, and one inside the tests with a mocked out client.

Code speaks louder than words. I built a Client which had a WebClient interface inside it. This interface supports a single method Get(). My test code just mocks this out and returns different test data based on the request URL, and the real client uses the real http.Client from the net/http Go library.

I am still unsure about how to write test code when using channels and goroutines. I used goroutines to execute HTTP requests in parallel and used channels to communicate success back once the asynchronous request had completed. I had to do a little refactoring to make sure the Get() method was implemented in a way where I was only doing the HTTP request. Initially my Get() method (after the first refactor) still did both the HTTP GET request and signalled back on the channel which crashed the test code, but beyond this, my code did not need to change much at all.

Check out the full code and tests on GitHub