What is httptest, and how do you use it to test HTTP handlers?
Quick Answer
net/http/httptest provides tools for testing HTTP code without a real network connection. httptest.NewRecorder() gives you an http.ResponseWriter implementation that captures whatever a handler writes (status code, headers, body) into memory, so you can call your handler directly and assert on the recorded response. httptest.NewServer(handler) spins up a real, local HTTP server on a random port backed by your handler, useful when testing code that needs an actual *http.Client making real HTTP requests (rather than calling the handler function directly). Both let you test HTTP-facing code fast and deterministically, without binding to a real network port for unit-level tests, or with a real (but local, ephemeral) server for integration-style tests.
Detailed Answer
httptest is the standard library's answer to "how do I test an HTTP handler without actually starting a server and making real network calls."
Testing a handler directly with NewRecorder
func helloHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name == "" {
name = "world"
}
fmt.Fprintf(w, "Hello, %s!", name)
}
func TestHelloHandler(t *testing.T) {
req := httptest.NewRequest("GET", "/hello?name=Alice", nil)
rec := httptest.NewRecorder()
helloHandler(rec, req)
if rec.Code != http.StatusOK {
t.Errorf("status = %d, want 200", rec.Code)
}
if body := rec.Body.String(); body != "Hello, Alice!" {
t.Errorf("body = %q, want %q", body, "Hello, Alice!")
}
}
No real network socket is opened at all — helloHandler is called as a plain function, and rec captures everything it writes for inspection.
Using a real local server with NewServer
func TestWithRealServer(t *testing.T) {
server := httptest.NewServer(http.HandlerFunc(helloHandler))
defer server.Close()
resp, err := http.Get(server.URL + "/hello?name=Bob")
if err != nil {
t.Fatal(err)
}
defer resp.Body.Close()
body, _ := io.ReadAll(resp.Body)
if string(body) != "Hello, Bob!" {
t.Errorf("got %q", body)
}
}
httptest.NewServer starts a real TCP listener on an available local port, useful when testing a client library or code that genuinely needs to make an HTTP round trip, not just call a handler function in-process.
When to use which
Use NewRecorder for fast, in-process handler tests — the overwhelming majority of handler-level unit tests. Reach for NewServer when the code under test needs a real *http.Client making an actual request. Testing a client wrapper library, or verifying behavior specific to the net/http transport, are typical cases.