Mocking HTTP requests in Go

Testing gives us the peace of mind to allow external contributors or even ourselves in the future to modify code without worrying about breaking any functionality. The problem is that when it comes to testing features that depend on making HTTP requests to third party services, we tend to just keep our eyes closed.

Very often, I see people, myself included, writing code that not only skips the HTTP request, but also the code that takes the response and manipulates it into something the application will use.

While it’s true that outside of sending an actual HTTP request in a test, it’s not possible to guarantee that the external service will respond exactly your code expects it to, not to mention it may not even be possible in some cases due to security constraints, we can at least make an assumption that the service will respond as per the API contract at the moment of implementation.

So, how do we test this? By mocking the HTTP client. That sounds complicated, I know, but it’s really simple.

Rather than mocking an entire HTTP client, we can mock the Client’s Transport field with a mocked round tripper.

The RoundTripper interface would normally be in charge of taking a Request and returning a Response, but since we’re not actually sending a request, this is very simple.

First, we want to create our MockRoundTripper. I like having this in a separate file named mock.go in the package test to reduce unnecessary dependencies, especially if you’re using this for a library (as opposed to in an application):

package test

import "net/http"

type MockRoundTripper func(r *http.Request) *http.Response

func (f MockRoundTripper) RoundTrip(r *http.Request) (*http.Response, error) {
    return f(r), nil
}

Now that we’ve got our MockRoundTripper, we need a way to inject it so that our entire application will use it.

You can either pass the client around as a parameter, or have a shared http.Client instance somewhere. I tend to use the latter for simplicity.

package client

import "net/http"

var sharedClient *http.Client

func GetHTTPClient() *http.Client {
    if sharedClient == nil {
        sharedClient = http.DefaultClient
    }
    return sharedClient
}

func InjectHTTPClient(httpClient *http.Client) {
    sharedClient = httpClient
}

Assuming you’re now using client.GetHTTPClient() to retrieve your HTTP client all over your application, your last step will be to inject a client with a MockRoundTripper in your tests.

Here’s an example of how to inject a mocked HTTP response:

client.InjectHTTPClient(&http.Client{
    Transport: test.MockRoundTripper(func(r *http.Request) *http.Response {
        return &http.Response{
            StatusCode: 200,
            Body:       io.NopCloser(strings.NewReader("Hello, World!")),
        },
    },
}

Okay, I’m bored now, and I don’t feel like continuing this article, but it should’ve gotten the point across.

If you want a working example, see TwiN/go-pastebin.