Day 045 — httptest for Handlers¶
Month 2 · Week 3 · ⬅ Day 044 · Day 046 ➡ · Journal index
🎯 Learning Objective¶
Test http.Handler code two ways with net/http/httptest: in-process with NewRecorder, and over a real loopback connection with NewServer.
📚 Topics¶
httptest.NewRequest+httptest.NewRecorder(no socket)httptest.NewServer/NewTLSServer(real ephemeral port)- Asserting status, headers, and body
📖 Reading / Sources¶
-
net/http/httptestdocs -
net/http— Handler / HandlerFunc - Go Blog — Testing HTTP clients/servers patterns
📝 Notes¶
- An
http.Handleris anything withServeHTTP(w http.ResponseWriter, r *http.Request). Because that's an interface, you can call it directly in a test — no server required. → [[http-handler]] httptest.NewRecorder()returns a*ResponseRecorder, anhttp.ResponseWriterthat capturesCode,Header(), andBody(a*bytes.Buffer). Callh.ServeHTTP(rec, req)then assert.httptest.NewRequest(method, target, body)builds a request wired for server-side handling (it panics on a bad target, which is fine in tests).rec.Result()gives an*http.Responsesnapshot; readres.Bodywithio.ReadAlland close it. Or inspectrec.Code/rec.Body.String()directly.httptest.NewServer(h)starts a real server on127.0.0.1:<random>; usesrv.URLwith a realhttp.Client. Alwaysdefer srv.Close(). Reach for it when testing a client, middleware tied to the transport, redirects, or TLS (NewTLSServer).- Prefer
NewRecorder— it's faster, deterministic, and avoids ports/goroutines. UseNewServeronly when you genuinely need the wire. - Set the
contexton a request withreq.WithContext(ctx)when the handler reads deadlines/values; the context is always the request's, not a separate arg.
💻 Code Examples¶
func TestHandler(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/greet?name=Ada", nil)
rec := httptest.NewRecorder()
Handler().ServeHTTP(rec, req) // call directly — no network
res := rec.Result()
defer res.Body.Close()
if res.StatusCode != http.StatusOK {
t.Fatalf("status = %d; want 200", res.StatusCode)
}
body, _ := io.ReadAll(res.Body)
if string(body) != "Hello, Ada!" {
t.Errorf("body = %q; want %q", body, "Hello, Ada!")
}
}
Runnable demo (both recorder + server):
examples/month-02/httptest-demo/· Run:go run ./examples/month-02/httptest-demoTested handler:exercises/month-02/week-3/greeter/
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
greeter.Handler with httptest table tests |
✅ | exercises/month-02/week-3/greeter |
Assert the Allow header on a 405 |
✅ | exercises/month-02/week-3/greeter |
🐛 Mistakes Made¶
- Forgot to
Close()the body fromrec.Result()— harmless for a recorder but a real leak againstNewServer; made it a habit. - Reached for
NewServerfirst; realizedNewRecordercovered everything except header-on-the-wire cases and was much faster.
❓ Open Questions¶
- For middleware that wraps
http.RoundTripper, do I test withNewServeror a fakeRoundTripper? (Both valid; a fake transport is more unit-y.)
🧠 Active Recall (answer without looking)¶
- Q: Why can you test a handler without starting a server?
A
http.Handler is an interface; you can construct a request and a ResponseRecorder (an http.ResponseWriter) and call ServeHTTP directly in-process.
2. Q: When is httptest.NewServer actually needed? A
When you need the real transport: testing an HTTP client, redirects, connection/TLS behavior, or middleware tied to the network layer.
🪶 Feynman Reflection¶
A handler is just a function that fills in a response object. httptest.NewRecorder hands it a fake response object you can read back, so you "call the website" without any networking. NewServer is the real thing on a throwaway port for when you must use an actual client.
🕳️ Knowledge Gaps¶
- TLS test servers and cert handling (
NewTLSServer) — note for later.
✅ Summary¶
I can unit-test handlers in-process with a recorder and integration-test over a real loopback server, asserting status, headers, and body.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 046: measuring coverage and a first look at the
testifyassertion library.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 90 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: test(week-3): httptest recorder and server (day 045)