Professional Documents
Culture Documents
questionable services
Technical writings about computing infrastructure, HTTP & security.
(by Matt Silverlock)
You’re building a web (HTTP) service in Go, and you want to unit
test your handler functions. You’ve got a grip on Go’s net/http
package, but you’re not sure where to start with testing that your
handlers return the correct HTTP status codes, HTTP headers or
response bodies.
Let’s walk through how you go about this, injecting the necessary
dependencies, and mocking the rest.
A Basic Handler
We’ll start by writing a basic test: we want to make sure our handler
returns a HTTP 200 (OK) status code. This is our handler:
// handlers.go
package handlers
// In the future we could report back on the status of our DB, or our cache
// (e.g. Redis) by performing a simple PING, and include them in the response.
io.WriteString(w, `{"alive": true}`)
}
https://blog.questionable.services/article/testing-http-handlers-go/ 1/8
4/20/2021 Testing Your (HTTP) Handlers in Go · questionable services
import (
"net/http"
"net/http/httptest"
"testing"
)
As you can see, Go’s testing and httptest packages make testing our
handlers extremely simple. We construct a *http.Request, a
*httptest.ResponseRecorder, and then check how our handler has
if token != expectedToken {
t.Errorf("token does not match: got %v want %v", token, expectedToken)
}
https://blog.questionable.services/article/testing-http-handlers-go/ 3/8
4/20/2021 Testing Your (HTTP) Handlers in Go · questionable services
to ‘port’ to other routers using their own types, the best routers are
those that are compatible with Go’s existing interfaces. chi and
gorilla/mux are my picks.
func TestGetProjectsHandler(t *testing.T) {
req, err := http.NewRequest("GET", "/api/users", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
// e.g. func GetUsersHandler(ctx context.Context, w http.ResponseWriter, r *http.R
handler := http.HandlerFunc(GetUsersHandler)
// Add our context to the request: note that WithContext returns a copy of
// the request, which we must assign.
req = req.WithContext(ctx)
handler.ServeHTTP(rr, req)
// e.g. middleware.go
func RequestIDMiddleware(h http.Handler) http.Handler {
fn := func(w http.ResponseWriter, r *http.Request) {
// More correctly, we'd use a const key of type struct{} and a random
// crypto/rand.
ctx := context.WithValue(r.Context(), "app.req.id", "12345")
h.ServeHTTP(w, r.WithContext(ctx))
}
return http.HandlerFunc(fn)
}
// e.g. middleware_test.go
func TestPopulateContext(t *testing.T) {
req, err := http.NewRequest("GET", "/api/users", nil)
if err != nil {
t.Fatal(err)
}
rr := httptest.NewRecorder()
// func RequestIDMiddleware(h http.Handler) http.Handler
// Stores an "app.req.id" in the request context.
handler := RequestIDMiddleware(testHandler)
handler.ServeHTTP(rr, req)
}
Running go test in our package should see this pass. The inverse of
this approach is also useful - e.g. testing that admin tokens aren’t
incorrectly applied to the wrong users or contexts aren’t passing
the wrong values to wrapped handlers.
Mocking Database Calls
// handlers_test.go
package handlers
rr := httptest.Recorder()
// Handler is a custom handler type that accepts an env and a http.Handler
// GetProjectsHandler here calls GetProject, and should raise a HTTP 500 if
// it fails.
handler := Handler{env, GetProjectsHandler)
handler.ServeHTTP(rr, req)
// We're now checking that our handler throws an error (a HTTP 500) when it
// should.
if status := rr.Code; status != http.StatusInternalServeError {
t.Errorf("handler returned wrong status code: got %v want %v"
rr.Code, http.StatusOK)
}
// We'll also check that it returns a JSON body with the expected error.
expected := []byte(`{"status": 500, "error": "Bad connection"}`)
if !bytes.Equals(rr.Body.Bytes(), expected) {
t.Errorf("handler returned unexpected body: got %v want %v",
rr.Body.Bytes(), expected)
}
https://blog.questionable.services/article/testing-http-handlers-go/ 6/8
4/20/2021 Testing Your (HTTP) Handlers in Go · questionable services
What Next?
Recent Posts
https://blog.questionable.services/article/testing-http-handlers-go/ 7/8
4/20/2021 Testing Your (HTTP) Handlers in Go · questionable services
© 2020 Matt Silverlock | His photo journal | Code snippets are MIT licensed | Built with Jekyll
https://blog.questionable.services/article/testing-http-handlers-go/ 8/8