Go cookbook · Beginner
Test HTTP handlers with httptest
Handler tests start real servers on ports or skip routing and middleware.
Approach
Build the real handler (mux plus middleware), send requests with httptest.NewRequest and a
ResponseRecorder, and assert status, headers and body. Use httptest.NewServer to fake external APIs.
Solution
package api
import (
"encoding/json"
"net/http"
"net/http/httptest"
"strings"
"testing"
)
func NewHandler() http.Handler {
mux := http.NewServeMux()
mux.HandleFunc("GET /items/{id}", func(w http.ResponseWriter, r *http.Request) {
if r.PathValue("id") != "1" {
http.Error(w, "not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"name": "Teapot"})
})
return mux
}
func TestGetItem(t *testing.T) {
h := NewHandler()
rec := httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/items/1", nil))
if rec.Code != http.StatusOK {
t.Fatalf("status = %d, want 200", rec.Code)
}
if got := strings.TrimSpace(rec.Body.String()); got != `{"name":"Teapot"}` {
t.Errorf("body = %s", got)
}
rec = httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodGet, "/items/2", nil))
if rec.Code != http.StatusNotFound {
t.Errorf("status = %d, want 404", rec.Code)
}
rec = httptest.NewRecorder()
h.ServeHTTP(rec, httptest.NewRequest(http.MethodDelete, "/items/1", nil))
if rec.Code != http.StatusMethodNotAllowed {
t.Errorf("status = %d, want 405", rec.Code)
}
}
Pitfalls
- Testing handler functions directly and missing routing and middleware behaviour.
- Comparing JSON as strings when field order or whitespace can change; decode and compare values.
- Tests calling real external services.
Variations
httptest.NewServer(handler)gives a real URL for clients that need one (fake third-party APIs).- Use
testing/synctest(Go 1.25) to test code with timers deterministically.
Further reading: httptest
Reference: https://pkg.go.dev/net/http/httptest
More Go recipes
- Benchmark code with testing.B.Loop
- Build a small, secure container image for a Go service
- Decode and validate JSON request bodies safely
- Embed static files and templates in the binary
- Load and validate configuration from environment variables
- Log structured JSON with slog and redact secrets
- Process jobs concurrently with a bounded worker pool
- Profile a running service with pprof