Keep the handler easy to call
A handler is just a function with an HTTP response writer and a request. You can exercise it directly, without starting a server or finding an unused port. This is useful for checking a response contract quickly.
func health(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
_, _ = w.Write([]byte(`{"status":"ok"}`))
}Assert behavior at the HTTP boundary
Put the test in a file ending in _test.go. This complete example needs encoding/json, net/http, net/http/httptest, and testing in its import block. Decoding the body checks the JSON data without depending on whitespace or key order.
func TestHealth(t *testing.T) {
request := httptest.NewRequest(http.MethodGet, "/health", nil)
recorder := httptest.NewRecorder()
health(recorder, request)
if recorder.Code != http.StatusOK {
t.Fatalf("status: got %d", recorder.Code)
}
if got := recorder.Header().Get("Content-Type"); got != "application/json" {
t.Fatalf("content type: got %q", got)
}
var body struct {
Status string `json:"status"`
}
if err := json.Unmarshal(recorder.Body.Bytes(), &body); err != nil {
t.Fatal(err)
}
if body.Status != "ok" {
t.Fatalf("status field: got %q", body.Status)
}
}Test routing separately when it matters
Calling the handler directly does not test router behavior. If the method, path, or middleware matters, pass the request to the configured router instead. Use httptest.NewServer when you need a real HTTP client interaction, redirects, or a round trip through a network listener.
Keep the useful cases
A second successful request is less useful than a missing parameter or a malformed body. Choose cases that describe a rule someone could accidentally break. For a JSON endpoint, check invalid input, the response status, and the shape of the returned data.
httptest package reference ↗