httptest — Specification¶
This file collects the godoc-level facts about net/http/httptest from src/net/http/httptest/ (httptest.go, recorder.go, server.go). Treat it as the reference card you check before writing test code.
Package summary¶
The package is part of the standard library and depends on net, net/http, crypto/tls, bufio, bytes, io, and sync. Source location: $GOROOT/src/net/http/httptest/.
It is split into three logical surfaces:
- Synthetic requests —
NewRequest. - In-process response capture —
ResponseRecorder,NewRecorder. - Loopback servers —
Server,NewServer,NewUnstartedServer,NewTLSServer.
httptest.NewRequest¶
Signature:
Documentation: "NewRequest returns a new incoming server Request, suitable for passing to an http.Handler for testing." It panics if method is invalid (per http.NewRequest's rules) or target cannot be parsed.
Key behaviors from httptest.go:
- The returned
*http.RequesthasRequestURIset, so it looks like a server-side request (handlers see what(*conn).readRequestwould have produced). Hostis set fromtargetif it contains an authority; otherwise it is"example.com".- If
bodyisnil,Bodyis set tohttp.NoBody. - If
bodyimplements an in-memory reader (*bytes.Reader,*bytes.Buffer,*strings.Reader),ContentLengthis set to the byte length; otherwise it is-1. Protois"HTTP/1.1",ProtoMajoris1,ProtoMinoris1.RemoteAddris"192.0.2.1:1234"(a TEST-NET-1 address from RFC 5737, by design unreachable).TLSisnilunlesstargetstarts withhttps://, in which case a minimal*tls.ConnectionStateis synthesised.
This is the server-side counterpart to http.NewRequest (which produces an outgoing client request). Do not confuse the two.
httptest.ResponseRecorder¶
Defined in recorder.go:
type ResponseRecorder struct {
Code int // the HTTP response code
HeaderMap http.Header // the HTTP response headers
Body *bytes.Buffer // if non-nil, the bytes.Buffer to append written data to
Flushed bool
// contains filtered or unexported fields
}
Important methods:
func NewRecorder() *ResponseRecorder— returns a recorder withCode: 200, an emptyHeaderMap, and a non-nilBody.func (rw *ResponseRecorder) Header() http.Header— implementshttp.ResponseWriter.func (rw *ResponseRecorder) Write(b []byte) (int, error)— appends toBodyif non-nil, otherwise discards. Implicitly callsWriteHeader(200)on first write.func (rw *ResponseRecorder) WriteString(s string) (int, error)— same, but for strings.func (rw *ResponseRecorder) WriteHeader(code int)— records the status code; further calls are ignored (perhttp.ResponseWriter).func (rw *ResponseRecorder) Flush()— setsFlushed = trueand callsWriteHeader(200)if not yet called.func (rw *ResponseRecorder) Result() *http.Response— returns a*http.Responsesnapshot built from the recorder. The response hasBodyset to aNopCloseraround theBodybuffer;StatusCode = Code;Headeris a clone ofHeaderMap;Protois"HTTP/1.1"; trailers populated from headers prefixed withTrailer:.
Result is safe to call after the handler returns. It is not safe to call concurrently with the handler still writing.
Subtlety: ResponseRecorder does not simulate every behavior of the real server's response writer. It does not enforce header validity, does not implement http.Hijacker, and does not implement http.CloseNotifier. It does implement http.Flusher via the Flush method above.
httptest.Server¶
Defined in server.go:
type Server struct {
URL string // base URL of form http://ipaddr:port with no trailing slash
Listener net.Listener
EnableHTTP2 bool // by default, HTTP/2 is not enabled
TLS *tls.Config // nil if not using TLS
Config *http.Server
// contains filtered or unexported fields
}
Fields you may write before Start/StartTLS:
EnableHTTP2— when true and TLS is enabled, the server advertises and serves HTTP/2.TLS— TLS configuration whenStartTLSis used;httptestwill mutate this to install the self-signed certificate.Config— the underlying*http.Server; modify timeouts,ErrorLog, etc. before starting.Listener— only meaningful forNewUnstartedServerif you replace it beforeStart.
Constructors¶
func NewServer(handler http.Handler) *Server
func NewTLSServer(handler http.Handler) *Server
func NewUnstartedServer(handler http.Handler) *Server
Behavior from server.go:
NewServercreates and starts an HTTP server listening on127.0.0.1:0(or[::1]:0if IPv4 is unavailable). Returns after the listener is open.NewTLSServerdoes the same but wraps the listener in TLS using an internally generated, self-signed certificate. Hostname in the cert isexample.com; SAN coversexample.comand127.0.0.1.NewUnstartedServerallocates theServerbut does not start it. Use this when you need to mutateConfigorEnableHTTP2before requests are accepted.
Methods:
func (s *Server) Start()
func (s *Server) StartTLS()
func (s *Server) Close()
func (s *Server) CloseClientConnections()
func (s *Server) Client() *http.Client
func (s *Server) Certificate() *x509.Certificate
Notes:
Startpanics if called on a server that is already running.StartTLSpanics if called twice or if called on anhttptest.NewServer(non-TLS) instance.Closeblocks until all outstanding requests return. It callsShutdownon the underlyinghttp.Serverafter closing the listener.CloseClientConnectionsforcibly closes any open keep-alive connections without waiting for handlers to return. Useful for testing client retry logic.Client()returns an*http.ClientwhoseTransporthas the server's certificate installed in itsRootCAspool. Using this client guarantees TLS verification succeeds against the test server.Certificate()returns the parsed leaf certificate the test server presents (only meaningful for TLS).
Self-signed certificate¶
The certificate used by NewTLSServer is generated by internal/testcert.LocalhostCert (see src/crypto/tls/generate_cert.go for the standalone tool that produces an analogous cert). It is hard-coded for the lifetime of the net/http/httptest package:
- Valid from
Jan 1 00:00:00 1970toDec 31 23:59:59 2049. - CN:
example.com. - SAN DNS names:
example.com,localhost. - SAN IPs:
127.0.0.1,::1. - Key type: 1024-bit RSA (historically; modern Go versions use a generated key per certain conditions).
- It is not an issuer; any verification that requires a chain to a CA must use
Server.Client()or installServer.Certificate()into a custom pool.
Concurrency guarantees¶
Server.Closeis safe to call from at.Cleanupregistered before any goroutine spawns; it serialises with the listener'sAcceptloop.ResponseRecorderis not safe for concurrent use; if the handler under test spawns goroutines that write to the same recorder, callers must add their own synchronisation.Serveritself can be hit concurrently; each accepted connection runs in its own goroutine inside the underlyinghttp.Server.
Versioning notes¶
httptest.NewRequestwas added in Go 1.7. Earlier code useshttp.ReadRequeston abufio.Readerover a raw HTTP/1.1 payload.Server.Client()andServer.Certificate()were added in Go 1.9.- The
EnableHTTP2field was added in Go 1.14. - The
ResponseRecorder.WriteStringmethod was added in Go 1.6.
Source files¶
src/net/http/httptest/
httptest.go // NewRequest
recorder.go // ResponseRecorder
server.go // Server, NewServer, NewTLSServer, NewUnstartedServer
httptest_test.go // tests
recorder_test.go
server_test.go
example_test.go
When in doubt, read recorder.go and server.go — they are short and authoritative.
Detailed field reference¶
ResponseRecorder fields¶
Code int Status code; defaults to 200.
HeaderMap http.Header Headers set by the handler.
Body *bytes.Buffer Body bytes; nil disables capture.
Flushed bool True if Flush() was called.
The unexported fields (not part of the API) include:
result *http.Response— cached snapshot returned byResult().snapHeader http.Header— header copy captured at first write.wroteHeader bool— guard against duplicateWriteHeadercalls.
Server fields¶
URL string Full base URL; empty until Start.
Listener net.Listener The bound listener; replaceable before Start.
EnableHTTP2 bool Negotiate HTTP/2; requires TLS.
TLS *tls.Config TLS configuration; mutated by StartTLS.
Config *http.Server Underlying http.Server; tunable before Start.
Unexported state on Server:
cert *x509.Certificate— the parsed leaf certificate.certPool *x509.CertPool— pool containing the test cert.wg sync.WaitGroup— tracks active connections for Close.client *http.Client— lazily built; returned byClient().
Methods on Server¶
func (s *Server) Start()
Starts the server. Panics if called twice.
func (s *Server) StartTLS()
Starts the server with TLS. Panics if called after Start.
func (s *Server) Close()
Closes the listener and waits for handlers. Idempotent in Go 1.20+.
func (s *Server) CloseClientConnections()
Forcibly closes idle and active client connections.
Does NOT close the listener.
func (s *Server) Client() *http.Client
Returns an HTTP client configured to talk to this server.
For TLS servers, the client trusts the test certificate.
func (s *Server) Certificate() *x509.Certificate
Returns the parsed leaf certificate (TLS only).
Behavioral guarantees¶
The package documents the following invariants:
- Listener is open before Start returns. Any code that does
httptest.NewServer(h)followed byhttp.Get(ts.URL)will not race on listener readiness. - Close blocks until all in-flight handlers return. A test that runs
ts.Close()is guaranteed the server is fully drained when the call returns. - URL has no trailing slash.
ts.URLishttp://127.0.0.1:portwithout a trailing/. Use string concatenation freely. - TLS cert pool is fresh. Each
NewTLSServermay use the same in-process cert (it's shared), butServer.Client()always returns a client with that cert in its pool. - NewRequest sets Host. Even if
targetis just/path,Hostisexample.com. Never leave it empty.
Source layout in detail¶
src/net/http/httptest/
httptest.go // NewRequest
httptest_test.go // tests for NewRequest
recorder.go // ResponseRecorder and friends
recorder_test.go // tests for ResponseRecorder
server.go // Server, NewServer, NewTLSServer, NewUnstartedServer
server_test.go // tests for Server
example_test.go // godoc examples
httptest.go is the smallest file in the package — under 100 lines including comments. recorder.go is roughly 200 lines. server.go is the largest, around 400 lines, mostly setup and TLS configuration.
Worth reading in order of usefulness:
recorder.go— the model for in-process tests.httptest.go— to understand request defaults.server.go— to understand TLS setup and Close semantics.example_test.go— to see idiomatic patterns.
Version history (selected)¶
| Go version | Change |
|---|---|
| 1.7 | Added httptest.NewRequest. |
| 1.9 | Added Server.Client() and Server.Certificate(). |
| 1.10 | Added (*Server).CloseClientConnections documented behavior. |
| 1.14 | Added Server.EnableHTTP2. |
| 1.17 | Removed deprecated *ResponseRecorder.HeaderMap recommendation; Header() is preferred. |
| 1.20 | (*Server).Close made idempotent. |
If your test must work across older Go versions, refer to the version note for each API.
Common values¶
- Default cert validity:
1970-01-01to2049-12-31. - Default
HostfromNewRequest:example.com. - Default
RemoteAddrfromNewRequest:192.0.2.1:1234. - Default
CodeonNewRecorder:200. - Default
Protoon requests and responses:HTTP/1.1.
These are not configurable. If your code depends on them (e.g. parses RemoteAddr for IP filtering), set them explicitly in the test.
Annotated example from example_test.go¶
The package's own example file demonstrates idiomatic use:
func ExampleResponseRecorder() {
handler := func(w http.ResponseWriter, r *http.Request) {
http.Error(w, "something failed", http.StatusInternalServerError)
}
req := httptest.NewRequest("GET", "http://example.com/foo", nil)
w := httptest.NewRecorder()
handler(w, req)
fmt.Printf("%d - %s", w.Code, w.Body.String())
// Output: 500 - something failed
}
func ExampleServer() {
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
fmt.Fprintln(w, "Hello, client")
}))
defer ts.Close()
res, err := http.Get(ts.URL)
if err != nil {
log.Fatal(err)
}
greeting, err := io.ReadAll(res.Body)
res.Body.Close()
if err != nil {
log.Fatal(err)
}
fmt.Printf("%s", greeting)
// Output: Hello, client
}
Three observations:
- The example uses
defer ts.Close()nott.Cleanup. In a real test,t.Cleanupis preferred. The example is in packagehttptest_test, not in a test function. - The example treats
res.Body.Closeseparately fromdefer— closing before reading the next variable. Either style works. - The expected output uses the
// Output:comment, whichgo testverifies. The example is also documentation; godoc renders it inline.
Comparison to other languages¶
For context, here is how httptest compares to its peers:
- Java (Spring Boot) —
MockMvcfor handler testing,TestRestTemplatefor client testing. Both have heavy framework dependencies. - Python (Flask) —
app.test_client()returns a synchronouswerkzeug.test.Clientthat wraps the app. Equivalent toResponseRecorderbut framework-coupled. - Node.js (Express) —
supertestwraps an Express app and sends real HTTP. Closer tohttptest.NewServer. - Ruby (Rack) —
Rack::Testprovideslast_response, similar toResponseRecorder.
Go's httptest is the only one of these that ships in the standard library and is fully framework-independent.
When NOT to use httptest¶
- Testing the standard library's own HTTP behavior — that's already covered by
net/http's own tests. - Testing a non-HTTP protocol (gRPC, WebSocket framing). Use protocol-specific harnesses.
- Load testing — for that, use
hey,vegeta, orwrkagainst a real server. - Contract testing — for that, use Pact or schema validation tools.
httptest is the right tool when:
- You wrote an
http.Handleror*http.Clientcode. - You want fast, deterministic, no-network tests.
- You want to inspect the response in detail.
Frozen-in-time guarantees¶
The following will not change across Go versions:
- The
ResponseRecorder.Codefield defaults to 200. NewRequestsetsRequestURIandRemoteAddr.NewServerbinds to127.0.0.1:0.Closeblocks until in-flight handlers return.Client()returns a fresh*http.Clientper call.
The following may change:
- The exact value of the test certificate (key size, expiry, signing algorithm). Use
Certificate()to read it; don't depend on hardcoded fingerprints. - The exact
RemoteAddrstring. Don't pattern-match on it. - Whether
Serveruses HTTP/2 by default withEnableHTTP2 = true(currently it does; future Go may negotiate automatically).
When in doubt, write tests that depend on observable behavior, not implementation details.