Go Basics: Exercise an HTTP Handler Without Opening a Port
Test a small HTTP handler directly before introducing a server or network client. The recorder captures its response, and a second request checks the unsupported-method branch.
Prepare a scratch directory
Tested on Linux amd64 with Go 1.26.10. Install Go and make go available on PATH. You also need Bash, a text editor, and the standard mktemp, rm, and rmdir utilities. The example uses only the standard library and needs no module or external dependency.
Keep the same terminal open. Stop if setup fails, before saving the source:
start_dir="$PWD"
work_dir="$(mktemp -d)" || exit 1
cd -- "$work_dir" || exit 1Run the example
Save this as httptest-handler-demo.go:
package main
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httptest"
)
func health(w http.ResponseWriter, r *http.Request) {
if r.Method != http.MethodGet {
w.Header().Set("Allow", http.MethodGet)
http.Error(w, "method not allowed", http.StatusMethodNotAllowed)
return
}
w.Header().Set("Content-Type", "application/json")
if err := json.NewEncoder(w).Encode(struct {
Status string `json:"status"`
}{"ok"}); err != nil {
return
}
}
func main() {
for _, method := range []string{http.MethodGet, http.MethodPost} {
response := httptest.NewRecorder()
health(response, httptest.NewRequest(method, "/health", nil))
fmt.Printf("%s: status=%d allow=%q body=%q\n", method, response.Code,
response.Header().Get("Allow"), response.Body.String())
if method == http.MethodGet {
fmt.Println("Content-Type:", response.Header().Get("Content-Type"))
}
}
}Run it:
go run httptest-handler-demo.goObserved output:
GET: status=200 allow="" body="{\"status\":\"ok\"}\n"
Content-Type: application/json
POST: status=405 allow="GET" body="method not allowed\n"Read the result
The GET request yields JSON, while POST gets 405 and an Allow header. A recorder implements ResponseWriter in memory, so neither request opens a listening socket. Setting headers before writing the body matters because the first write commits the response status.
Apply it carefully
Direct handler calls do not exercise routing, middleware, TLS, or network timeouts. Add those layers to your test explicitly when needed. A real service should observe response-write errors; after bytes have been sent, it cannot reliably replace the response with a new status.
The official Go reference documents the behavior used here.
Clean up
Remove the saved source, return to the starting directory, and remove the empty practice directory. go run does not leave an executable here; Go manages its build cache separately.
rm -- httptest-handler-demo.go
cd -- "$start_dir" || exit 1
rmdir -- "$work_dir"
unset start_dir work_dirIf the directory is not empty, inspect its remaining contents before removing anything else.
Acknowledgements
This post was created with assistance from AI.