Test HTTP via httptest.NewServer, not gock/httpmock
byob-http-client.4
concurrencyhttptesting
Problem: HTTP mocking libraries (gock, httpmock) monkey-patch
the default transport or install package-level interceptors. That
fights byob-testing.1 (inject test doubles through the Factory, never
monkey-patch globals) and hides real behavior — connection reuse,
header handling, content-length mismatches — behind a mock layer
that doesn't match production.
Idea: tests construct a real server with httptest.NewServer,
point the client at its .URL, and write a http.Handler that
returns the canned responses. The Factory's HTTPClient closure is
overridden with one whose base URL is the test server. This mirrors
byob-storage.6 (real sqlite backend in tests) — same discipline, same
payoff: the test exercises the production code path.
Tradeoffs: slightly more code per test than gock's
Mock().Get().Reply(). In exchange, tests catch real issues
(response-body-not-closed, incorrect Content-Type parsing, retry
behavior under server 503) that a mock library abstracts away. Real
network loopback on localhost is fast enough that parallel tests
don't care.
Design
func TestListItems(t *testing.T) {
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path != "/items" {
http.Error(w, "not found", http.StatusNotFound); return
}
w.Header().Set("Content-Type", "application/json")
fmt.Fprintln(w, `{"items":[{"id":1,"name":"a"}]}`)
}))
t.Cleanup(srv.Close)
f := testFactory(t, srv.URL) // Factory whose HTTPClient points at srv.URL
items, err := listItems(t.Context(), f)
// ... assertions
}
For flaky-network simulations (retry tests), respond 503 with a
Retry-After on the first attempt and 200 on the second, counting
requests with an atomic.