⛺ Home

Testing HTTP Clients using the RoundTripper interface

I have a pet pieve when it comes to testing Go HTTP Clients. Often I will see something like this:


type HTTPDoer interface {
    Do(*http.Request) (*http.Response, error)
}

type Service struct {
    Client HTTPDoer
}

func (svc Service) ExampleFunc() {
    var req *http.Reqest // Let's imagine this request is properly initiated
    resp, err := svc.Client.Do(req)
    // ...
}
  

In and of itself there's nothing inherently wrong with this, it does accomplish our goal of being able to pass a mock implementation of our client that can return any response we want. In production code we would initialize our service with a fulfledged *http.Client .

What I do not like, is that in a real world application the *http.Client contains behavior that may be important to test. It may contain it's own transport wrappers, a cookie jar, the redirect strategy, and timeout behavior.

The best part is that the standard library allows you to modify the RoundTripper of the client so that you can mock the network side of the client, but still use a standard http client in your tests.


// This code is defined within net/http but putting it here for clarity.
type RoundTripper interface {
    RoundTrip(*http.Request) (*http.Response, error)
}


type Service struct {
    Client *http.Client
}

func TestService(t *testing.T) {
    var (
		myMockTransport RoundTripperMock // RoundTripperMock must implement RoundTripper
		client          = &http.Client{Transport: &myMockTransport}
		svc             = Service{Client: client}
	)

    svc.ExampleFunc()
}
  

How you mock your transport is up to you, either my implementing a mock implementation manually or using your prefered mock generation tool. Personally I use moq but to each their own!

Now our code can test various builtin Client behaviors, and the intention of our code read wells. We don't have arbitrary Doer interfaces littered about our code. When we need an http client we use a *http.Client .

Happy testing!