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).
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
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:
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.
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
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: