DEV Community

Stephen Solka
Stephen Solka

Posted on • Edited on

Mocking http clients in Go

If you ever find yourself needing intercept a http client in go, httptest provides a facility to do just that. httptest.NewServer lets you stand up a temporary server that you can control in your test case.

We will use this function as an example of writing unit tests that use httptest.NewServer.

func topPostOnSubreddit(r string) (string, error) {
    resp, err := http.Get(fmt.Sprintf("https://reddit.com/r/%s", r))
    if err != nil {
        return "", err
    }
    defer resp.Body.Close()

    dec := json.NewDecoder(resp.Body)
    var data redditData
    err = dec.Decode(&data)
    if err != nil {
        return "", err
    }

    top := findTopPost(data)
    return top["title"].(string), nil
}
Enter fullscreen mode Exit fullscreen mode

Refactor

Let refactor this so we can use httptest.NewServer in our unit tests. The required changes are ...

An instance of http client has to be passed in

func topPostOnSubreddit(client *http.Client, r string) (string, error) {
    req, err := http.NewRequest("GET", fmt.Sprintf("https://reddit.com/r/%s", r), nil)
    if err != nil {
        return "", err
    }
    resp, err := client.Do(req)
    // ...
}
Enter fullscreen mode Exit fullscreen mode

The base of the url has to be configurable by the caller

func topPostOnSubreddit(client *http.Client, fullUrl string) (string, error) {
    req, err := http.NewRequest("GET", fullUrl, nil)
    if err != nil {
        return "", err
    }
    resp, err := client.Do(req)
    // ...
}
Enter fullscreen mode Exit fullscreen mode

Writing the Test Case

The returned test server has both the client and url available for you to pass to the function. When the function uses the client makes the http call the handlerFunc you defined in your test case is called. This allows you to inject fixture data into the runtime to control unit test behavior.

func TestTopPostOnSubreddit(t *testing.T) {
    server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        enc := json.NewEncoder(w)
        // this is where your test fixture goes
        testData := redditData{}
        err := enc.Encode(&testData)
        require.NoError(t, err)
    }))
    t.Cleanup(server.Close)

    url := fmt.Sprintf("%s/r/android.json", server.URL)
    top, err := topPostOnSubreddit(server.Client(), url)
    require.NoError(t, err)
    require.Equal(t, "foobar", top)
}
Enter fullscreen mode Exit fullscreen mode

Happy Testing!

Top comments (0)