Go

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

All 38 Performance, Profiling & Testing questions