A test that checks only 200 can miss a completely wrong body. A test that checks only a body can miss the wrong status or content type. Define the full exchange contract and compare the fields that matter.
This lesson’s probe invokes the supplied handler directly through httptest.NewRequest and httptest.NewRecorder. No listener, DNS or client socket is involved. That makes the check deterministic and appropriate for the hosted sandbox.
The fixture table is supplied. It includes health GET/HEAD, the empty collection, a method failure and an unknown route. Complete the status/body assertion, then inspect the separate media, nosniff, Allow and HEAD metadata checks.
The probe assumes a fresh empty repository. It is not suitable for an arbitrary populated service, and it does not claim to monitor production uptime. It deliberately performs no create/delete operation.
package main
import ("fmt";"net/http";"net/http/httptest")
func main(){
handler:=http.HandlerFunc(func(w http.ResponseWriter,r *http.Request){w.Header().Set("Content-Type","application/json");w.WriteHeader(404);_,_=w.Write([]byte(`{"error":"not_found"}`))});rec:=httptest.NewRecorder();handler.ServeHTTP(rec,httptest.NewRequest("GET","/missing",nil));fmt.Println(rec.Code,rec.Header().Get("Content-Type"))
}Prove the probe itself can fail. Feed it plausible broken handlers: one returns success for a missing route, one has the right status and wrong body, one omits Allow, and one writes a HEAD body or wrong representation length. Each should produce an error.
Keep error context useful and bounded: method and known path identify the exchange. Do not paste raw request bodies, credentials or private backend errors into public failure messages.
Handler tests answer a narrow but useful question. The downloadable lab separately tests an ephemeral loopback listener and shutdown adapter. Neither test type proves authentication, persistence or reverse-proxy deployment behavior that the lab does not implement.
package main
import ("fmt")
func main(){
expected,actual:="[]\n","null\n";fmt.Println(expected==actual,len(expected),len(actual))
}Complete the assertion and run both the good fixture and broken candidate table. Check that the probe never writes data and that a body-only defect cannot slip through a status check.
Next we connect routing, bounded decoding, repository calls and JSON responses in the complete service.
Follow one request through the service.
These seven exchanges were generated by the downloadable Go replay against one fresh in-memory store. Choose a recorded step to inspect its request, response and resulting note count. Choosing a step does not send a live request.
- Client requestused
- Route & methodused
- Bounded decoderused
- Repositoryused
- JSON responseused
Request
POST /notes
Content-Type: application/json{
"title": " learn "
}Recorded response
{
"Content-Length": "25",
"Content-Type": "application/json; charset=utf-8",
"Location": "/notes/1",
"X-Content-Type-Options": "nosniff"
}{
"id": 1,
"title": "learn"
}Headers, status, body and stored count are actual recorder results. The highlighted stages annotate the corresponding code path; this is a replay, not a distributed trace. The duplicate-key step leaves the stored count at one.
Download the runnable service, replay and tests →