Testing Go Handlers with Table-Driven Tests

1 min read
#go #testing

The standard library gives you most of what you need for HTTP tests: httptest, testing, and a clear table of inputs.

Build a Small Test Harness

func TestHealthHandler(t *testing.T) {
    tests := []struct {
        name string
        path string
        want int
    }{
        {name: "ok", path: "/healthz", want: http.StatusOK},
        {name: "missing", path: "/missing", want: http.StatusNotFound},
    }

    mux := http.NewServeMux()
    mux.HandleFunc("GET /healthz", healthHandler)

    for _, tt := range tests {
        t.Run(tt.name, func(t *testing.T) {
            req := httptest.NewRequest(http.MethodGet, tt.path, nil)
            rec := httptest.NewRecorder()
            mux.ServeHTTP(rec, req)

            if rec.Code != tt.want {
                t.Fatalf("status = %d, want %d", rec.Code, tt.want)
            }
        })
    }
}

Keep Assertions Focused

Test status codes, response contracts, and important headers. Avoid asserting every byte unless the exact output is part of the contract.

Add Failure Cases Early

Handlers usually fail in boring ways: invalid JSON, missing IDs, invalid methods, and dependency timeouts. Put those in the table before the happy path gets too comfortable.