I have written some posts on building images in an HTTP response and how to cache them. This post is about how to test all of this and verify that you are testing the right thing.

This is the code that we are starting with:


func blackHandler(w http.ResponseWriter, r *http.Request) {

key := "black"
e := `"` + key + `"`
w.Header().Set("Etag", e)
w.Header().Set("Cache-Control", "max-age=2592000") // 30 days

if match := r.Header.Get("If-None-Match"); match != "" {
if strings.Contains(match, e) {
w.WriteHeader(http.StatusNotModified)
return
}
}

m := image.NewRGBA(image.Rect(0, 0, 240, 240))
black := color.RGBA{0, 0, 0, 255}
draw.Draw(m, m.Bounds(), &image.Uniform{black}, image.ZP, draw.Src)

var img image.Image = m
writeImage(w, &img)
}

writeImage is a function that writes an image into a http.ResponseWriter (to read more about this go to my previous post Playing with images in HTTP response in golang).

Testing the handler:

Lets start by making a first test to blackHandler. This is done by using the httptest package. We call the NewRecorder() function which creates a ResponseRecorder object.

This is great because you can use the recorder as any http.ResponseWriter and also use it to check the response that comes from the handler. In this case, we can check that the HTTP response code is equal to http.StatusOK


package main

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

func TestBlackHandler(t *testing.T) {

blackHandlerFunc := http.HandlerFunc(blackHandler)
if r, err := http.NewRequest("GET", "", nil); err != nil {
t.Errorf("%v", err)
} else {
recorder := httptest.NewRecorder()
blackHandlerFunc.ServeHTTP(recorder, r)
if recorder.Code != http.StatusOK {
t.Errorf("returned %v. Expected %v.", recorder.Code, http.StatusOK)
}
}
}

Lets run our test.


    $ go test
PASS
ok local/cache 0.011s

local/cache is the path of my project in $GOPATH/src/local/cache

Test coverage:

Now lets measure our test coverage for this test. Go comes with a tool called cover and a great article that explains all you need to know about it.

Lets start running the test coverage:


    $ go test -cover
PASS
coverage: 57.7% of statements
ok local/cache 0.012s

We know we are not covering the main() function. This might explain why we have a lower percentage of coverage. Lets see what our test really covers:


    go test -coverprofile=coverage.out
go tool cover -html=coverage.out

This will open the following view in your browser:

coverage image of blackHandler

This is quite interesting, our test is covering most of the blackHandler function, but not the code that deals with the cache.

We need to improve our test so that the cache code is also covered.

Testing the cache:

To test the cache code we need to build a second request with the Etag of the first request (If this is new to you, you might want to check Learning HTTP caching in Go).

This can easily be done with the ResponseRecorder type from the httptest package, since the ResponseRecorder is an implementation of the http.ResponseWriter. So you can read information from it like you would do with a http.ResponseWriter type.

You can read the Header for the Etag value.


    // record etag to test cache
etag = recorder.Header().Get("Etag")

We then use the Etag when building our second request by setting it in the If-None-Match in the http.Header.


if r, err := http.NewRequest("GET", "", nil); err != nil {
t.Errorf("%v", err)
} else {
r.Header.Set("If-None-Match", etag)
...

The last thing to do is to check this time that the response.Code is equal to http.StatusNotModified.

This is how our test looks now:


package main

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

func TestBlackHandler(t *testing.T) {

blackHandlerFunc := http.HandlerFunc(blackHandler)
var etag string

// first request
if r, err := http.NewRequest("GET", "", nil); err != nil {
t.Errorf("%v", err)
} else {
recorder := httptest.NewRecorder()
blackHandlerFunc.ServeHTTP(recorder, r)
if recorder.Code != http.StatusOK {
t.Errorf("returned %v. Expected %v.", recorder.Code, http.StatusOK)
}
// record etag to test cache
etag = recorder.Header().Get("Etag")
}

// test caching
if r, err := http.NewRequest("GET", "", nil); err != nil {
t.Errorf("%v", err)
} else {
r.Header.Set("If-None-Match", etag)
recorder := httptest.NewRecorder()
blackHandlerFunc.ServeHTTP(recorder, r)
if recorder.Code != http.StatusNotModified {
t.Errorf("returned %v. Expected %v.", recorder.Code, http.StatusNotModified)
}
}
}

Let's see our coverage now:


    $ go test -cover
PASS
coverage: 69.2% of statements
ok local/cache 0.010s

That is better, lets check the coverage of the blackHandler function:


    go test -coverprofile=coverage.out
go tool cover -html=coverage.out

coverage image

That looks nice! :) As I said before, the reason why we do not get 100% coverage is because there is no test covering main() and error handling code in writeImage(). But the important part was to properly test the blackHander function.

The final example is available in this gist

I hope you found this post useful and that this will help you build proper tests for your applications.

Here are some resources that helped me come up with this article:


Follow me at @santiago_arias to be notified about more posts like this.

Santiaago