How to test a Go HTTP API with httptest
Test a Go HTTP API with the standard library: write a handler, capture responses with httptest.ResponseRecorder, run end-to-end tests with httptest.NewServer, and use table-driven cases.
What httptest gives you
Go's standard library includes net/http/httptest, which lets you test HTTP handlers without opening a real network port. A ResponseRecorder captures what a handler writes, and httptest.NewServer spins up a real but local server for end-to-end tests. Combined with Go's built-in testing package, you get fast, dependency-free API tests.
Prerequisites
- Go 1.21 or later
- Basic Go and HTTP knowledge
Steps
1. Set up the module
mkdir api && cd api
go mod init example.com/api
2. Write an HTTP handler
package api
import (
"encoding/json"
"net/http"
)
func HealthHandler(w http.ResponseWriter, r *http.Request) {
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}
3. Test with ResponseRecorder
The recorder captures the response so you can assert on it directly.
package api
import (
"net/http"
"net/http/httptest"
"testing"
)
func TestHealthHandler(t *testing.T) {
req := httptest.NewRequest(http.MethodGet, "/health", nil)
rec := httptest.NewRecorder()
HealthHandler(rec, req)
if rec.Code != http.StatusOK {
t.Fatalf("got status %d, want 200", rec.Code)
}
}
4. Test with a test server
For a full round trip through the HTTP stack, start a test server.
srv := httptest.NewServer(http.HandlerFunc(HealthHandler))
defer srv.Close()
resp, _ := http.Get(srv.URL)
5. Use table-driven cases
Drive multiple requests from a slice of structs to cover many paths and methods concisely.
6. Run tests and coverage
go test ./...
go test -cover ./...
Verification
Run go test ./... and confirm the handler test passes. Add a case asserting the response body contains "status":"ok". Change the handler to return a wrong status and confirm the test fails with a clear message.
Next Steps
Test middleware, inject dependencies through the handler's struct, add table-driven cases for error paths, and run go test -race ./... in continuous integration.