Skip to content
Go Basics: Exercise an HTTP Handler Without Opening a Port

Go Basics: Exercise an HTTP Handler Without Opening a Port

October 31, 2026

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 1

Run 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.go

Observed 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_dir

If the directory is not empty, inspect its remaining contents before removing anything else.

Acknowledgements

This post was created with assistance from AI.

Last updated on