Golang httptest Example

Go (Golang) struck me when I first learned about its HTTP testing package. In other programming languages I have been used to have to do a lot of work for testing HTTP servers. In Go (Golang) this isn’t the case. There is an amazing testing package that can be used right away from the standard library and it’s extremely easy to understand. Let’s have a look at how you can use the httptest package and build better end-to-end and http tests for your Go http services.

The idea behind the httptest package is that it’ pretty easy to mock an HTTP server or to mock a response writer to feed into our server handlers. This makes it extremely easy to test http servers and clients.

In this article we are going to explore two specific scenarios where the httptest package can be useful

Testing Go HTTP Server Handlers

Let’s see an example of our http server in Go. Here below you can find an example of a server that takes a word and returns the upper case version of the same.

package main

import (
    "fmt"
    "log"
    "net/http"
    "net/url"
    "strings"
)

// Req: http://localhost:1234/upper?word=abc
// Res: ABC
func upperCaseHandler(w http.ResponseWriter, r *http.Request) {
    query, err := url.ParseQuery(r.URL.RawQuery)
    if err != nil {
        w.WriteHeader(http.StatusBadRequest)
        fmt.Fprintf(w, "invalid request")
        return
    }
    word := query.Get("word")
    if len(word) == 0 {
        w.WriteHeader(http.StatusBadRequest)
        fmt.Fprintf(w, "missing word")
        return
    }
    w.WriteHeader(http.StatusOK)
    fmt.Fprintf(w, strings.ToUpper(word))
}

func main() {
    http.HandleFunc("/upper", upperCaseHandler)
    log.Fatal(http.ListenAndServe(":1234", nil))
}

In this scenario we want to test the upperCaseHandler logic that is used for our Go server. We will need two things to test that:

NewRequest returns a new incoming server Request, suitable for passing to an http.Handler for testing.

ResponseRecorder is an implementation of http.ResponseWriter that records its mutations for later inspection in tests.

Using httptest.ResponseRecorder

The httptest.ResponseRecorder is an implementation of http.ResponseWriter and can be used to be passed into our server handler, record all the data that the handler will write to the response and return the data written afterwards. Let’s see how that works in the example test below.

package main

import (
    "io/ioutil"
    "net/http"
    "net/http/httptest"
    "testing"
)

func TestUpperCaseHandler(t *testing.T) {
    req := httptest.NewRequest(http.MethodGet, "/upper?word=abc", nil)
    w := httptest.NewRecorder()
    upperCaseHandler(w, req)
    res := w.Result()
    defer res.Body.Close()
    data, err := ioutil.ReadAll(res.Body)
    if err != nil {
        t.Errorf("expected error to be nil got %v", err)
    }
    if string(data) != "ABC" {
        t.Errorf("expected ABC got %v", string(data))
    }
}

This will allows us to test the handler response by inspecting the ResponseRecorder output returned by the Result method

func (rw *ResponseRecorder) Result() *http.Response

Result returns the response generated by the handler.
The returned Response will have at least its StatusCode, Header, Body, and optionally Trailer populated. More fields may be populated in the future, so callers should not DeepEqual the result in tests.

Testing Go HTTP Clients

Testing Go server handlers is relatively easy, especially when you want to test just the handler logic. You just need to mock http.ResponseWriter and http.Request objects in your tests. When working with a Go http client though, things are a little more complicated. The reason is that sometimes is not as easy to mock or replicate an entire server. Let’s see our example below.

package main

import (
    "io/ioutil"
    "net/http"

    "github.com/pkg/errors"
)

type Client struct {
    url string
}

func NewClient(url string) Client {
    return Client{url}
}

func (c Client) UpperCase(word string) (string, error) {
    res, err := http.Get(c.url + "/upper?word=" + word)
    if err != nil {
        return "", errors.Wrap(err, "unable to complete Get request")
    }
    defer res.Body.Close()
    out, err := ioutil.ReadAll(res.Body)
    if err != nil {
        return "", errors.Wrap(err, "unable to read response data")
    }

    return string(out), nil
}

Our Go client requires a base URL which represents a remote server base URL. It then calls the method /upper with a given input word and returns the result to the caller as a string, or an error in case the request did not succeed. In order to test this code we will need to mock the entire server logic or at least the logic that is responsible for a given request path, like in our case the /upper path. With the httptest package we can mock the entire server by initialising a local server listening on the loopback interface which will return anything we want.

Using httptest.Server

The type we are going to use is the httptest.Server type by calling the httptest.NewServer function.

A Server is an HTTP server listening on a system-chosen port on the local loopback interface, for use in end-to-end HTTP tests.

func NewServer(handler http.Handler) *Server

NewServer starts and returns a new Server. The caller should call Close when finished, to shut it down.

Let’s see how we can do that in the example below.

package main

import (
    "fmt"
    "net/http"
    "net/http/httptest"
    "strings"
    "testing"
)

func TestClientUpperCase(t *testing.T) {
    expected := "dummy data"
    svr := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        fmt.Fprintf(w, expected)
    }))
    defer svr.Close()
    c := NewClient(svr.URL)
    res, err := c.UpperCase("anything")
    if err != nil {
        t.Errorf("expected err to be nil got %v", err)
    }
    // res: expected\r\n
    // due to the http protocol cleanup response
    res = strings.TrimSpace(res)
    if res != expected {
        t.Errorf("expected res to be %s got %s", expected, res)
    }
}

In the example above we just created a new mock server using the httptest.NewServer function, assigned it a custom mock handler that returns always the same data and used the server URL as our client’s server URL. This allows us to mock and instruct the server to return anything we want for testing purposes.

Checkout this example source code on the Go Playground https://play.golang.org/p/xQ6xRbyvOJQ

Go httptest YouTube Tutorial

IMAGE_ALT

Join the Golang Developers Community on Golang Cafe