Use Real Fake HTTP Servers in Unit Tests

Credit: http://www.mock-server.com

I spent a couple of hours wrangling with jarcoal/httpmock on an existing project that was using it quite heavily. Don’t get me wrong, httpmockis actually super awesome. However, I had a fairly complex setup with multiple mocked servers that needed to talk to each other and it was making my unit tests brittle and hard to understand.

I decided to rip out the existing mocks and use real HTTP servers. Which are still mocks, but are a lot more palatable to code that is using HTTP clients. Since I only needed to provide endpoints through to my code so it would know where to send requests.

These tools are now available in tf v1.7.0. Here is a simple example:

// 0 means to use a random port, or you can provide your own.
server := tf.StartHTTPServer(0).
AddHandlers(map[string]http.HandlerFunc{
"/hi": func(w http.ResponseWriter, r *http.Request) {
w.Write([]byte(`hello`))
},
"/easy": tf.HTTPJSONResponse(200, []int{1, 2, 3}),
})
// Always remember to tear down the resources when the test ends.
defer server.Shutdown()
// Your test code here...
server.Endpoint() // http://localhost:61223

Using a real HTTP server has some added benefits:

  1. It’s isolated. That means it doesn’t interfere in anyway with the global HTTP muxer or transport, such as intercepting requests. This was one of my problems with HTTP clients that needed to use the real or fake Transport. I’m aware httpmockhas ways of controlling which requests are allow to go outbound, this but I hit a deadlock when two mocked handlers tried to talk to each other. That was difficult to debug.
  2. It can be used in parallel. You can either share the same HTTPServeracross many tests (such as in TestMain), or create one for each test. Either case running them in parallel. Providing 0for the port (as in the example above) will ensure that it always selects an unused random port.
  3. It mutable. After creating and starting the HTTPServeryou can add/remove handlers. This is useful when most tests need some base logic, however some cases need to do special things under specific scenarios.

You can create your own handlers, of course, but there are a few common ones that also ship with tf:

  • HTTPEmptyResponse(statusCode int)
  • HTTPStringResponse(statusCode int, body string)
  • HTTPJSONResponse(statusCode int, body interface{})