How do you test HTTP handlers and HTTP clients with net/http/httptest?
Question 385MediumGo 1.22 to 1.25
Handlers: call the handler directly with httptest.NewRequest and httptest.NewRecorder — no network, fast. Clients: spin up httptest.NewServer (real loopback listener) returning canned responses, and point the client's base URL at srv.URL.
func TestHelloHandler(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/hello?name=gopher", nil)
rec := httptest.NewRecorder()
helloHandler(rec, req)
res := rec.Result()
if res.StatusCode != http.StatusOK {
t.Fatalf("status = %d, want 200", res.StatusCode)
}
if got := rec.Body.String(); got != "hello, gopher\n" {
t.Errorf("body = %q", got)
}
}
func TestClientRetriesOn503(t *testing.T) {
var hits atomic.Int32
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if hits.Add(1) < 3 {
w.WriteHeader(http.StatusServiceUnavailable)
return
}
fmt.Fprint(w, `{"ok":true}`)
}))
t.Cleanup(srv.Close)
c := NewClient(srv.URL, srv.Client())
if err := c.Ping(t.Context()); err != nil {
t.Fatal(err)
}
if n := hits.Load(); n != 3 {
t.Fatalf("hits = %d, want 3", n)
}
}
Gotchas: the server handler runs on another goroutine, so use atomics/mutexes for shared counters (and run -race). Test through your router, not just the bare handler, to cover middleware and path params. httptest.NewTLSServer plus srv.Client() gives a client that trusts its cert.
More on Performance, Profiling & Testing
- Q383What is testing/synctest and what problem does it solve?
- Q384How do you mock dependencies in Go without a mocking framework? Where should the interface live?
- Q386How does test coverage work in Go? Explain covermode, -coverpkg, and coverage for integration tests.
- Q387What is Profile-Guided Optimization (PGO) in Go, and how do you use it?
- Q388How does inlining work in the Go compiler, how do you inspect it, and what prevents a function from being inlined?
- Q389Explain escape analysis. Which of these allocate on the heap?