httptest and API Testing Patterns
httptest and API Testing Patterns
Overview
net/http/httptest tests handlers without a real network. Combine with table tests and fake dependencies for fast API coverage.
Handler unit test
func TestHealth(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/healthz", nil)
rec := httptest.NewRecorder()
healthz(rec, req)
if rec.Code != 200 {
t.Fatalf("code %d", rec.Code)
}
}Full mux
srv := NewServer(fakeStore)
req := httptest.NewRequest(http.MethodGet, "/api/books", nil)
rec := httptest.NewRecorder()
srv.Handler().ServeHTTP(rec, req)JSON helpers
func decodeJSON[T any](t *testing.T, rec *httptest.ResponseRecorder) T {
t.Helper()
var v T
if err := json.NewDecoder(rec.Body).Decode(&v); err != nil {
t.Fatal(err)
}
return v
}httptest.Server (integration-ish)
// Go 1.27+: in-memory network, t.Cleanup, synctest-safe.
ts := httptest.NewTestServer(t, handler)
res, err := ts.Client().Get("http://example.com/api/books")Prefer NewTestServer for new tests. httptest.NewServer still listens on loopback when you need a real TCP URL:
ts := httptest.NewServer(handler)
t.Cleanup(ts.Close)
res, err := http.Get(ts.URL + "/api/books")Still in-process; good middle ground before Testcontainers.
Rules of thumb
| Do | Don’t |
|---|---|
| Fake stores at interfaces | Real network in unit tests |
| Table-drive status codes | One mega test for all routes |
t.Helper on asserts |
Ignore body on 500s |
Try next
- Table test 200/400/401 for create book.
- Cookie session test.
- Assert
Content-Typeon JSON APIs.