httpxtest

July 14, 2026 ยท View on GitHub

sink-logo

httpxtest

httpxtest provides a small builder for spinning up httptest servers with less boilerplate.

It is designed to help you stand up predictable HTTP servers in tests by:

  • defining a default response for any request
  • defining responses for specific method/route pairs
  • responding with typed values, raw bytes, strings, or JSON
  • shaping responses with headers, cookies, content types, and delays
  • serving over plain HTTP or TLS

Overview

Use httpxtest when you want test server setup to be easy to read and reuse.

It wraps net/http/httptest so you can describe what a server should return instead of wiring up handlers, mux routing, and cleanup by hand. The builder registers t.Cleanup for you, so the server is closed automatically when the test finishes.

It is especially useful when:

  • you are testing client code that needs a server to respond in a specific way
  • the same server setup appears across multiple tests
  • you want to assert behavior around status codes, headers, cookies, or latency

When to use it

Use httpxtest when:

  • you are writing tests that need a real HTTP endpoint
  • you want to reduce httptest setup boilerplate
  • you want a consistent pattern for canned responses across a test suite

Prefer a plain httptest.Server when:

  • you need full control over the handler or routing logic
  • the test depends on behavior the builder does not model
  • a builder would obscure important details of the test

API reference

Build a server

FunctionPurpose
NewServerBuilderCreates a new ServerBuilder bound to a *testing.T
BuildStarts an HTTP test server and registers cleanup
BuildTLSStarts an HTTPS test server and registers cleanup

Define responses

MethodPurpose
OnSets the default handler to return a status code and response
OnFuncSets the default handler to a custom http.HandlerFunc
OnSequenceSets the default handler to an ordered sequence of responses
OnRouteRegisters a handler for a method/route that returns a status code and response
OnRouteFuncRegisters a custom http.HandlerFunc for a method/route
OnRouteSequenceRegisters an ordered sequence of responses for a method/route

OnRoute, OnRouteFunc, and OnRouteSequence panic if the method is empty or if a handler is already registered for the same method/route pair. On, OnFunc, and OnSequence panic if a default handler is already defined. Requests that do not match a registered route fall through to the default handler, which returns 500 Internal Server Error unless overridden with On, OnFunc, or OnSequence.

Sequenced responses

OnSequence and OnRouteSequence return a series of responses in order, advancing one entry per request. Each entry is built with the Response helper, which takes the same status code, response value, and options as On. This is useful for simulating a resource that changes between requests, such as a job that transitions from pending to done, or for testing client retry logic.

httpxtest.Response(http.StatusOK, myStruct{Name: "pending"}, httpxtest.WithJSONContentType())

The first argument to both methods is an ExhaustBehavior that controls what happens once every entry in the sequence has been served:

BehaviorBehavior once exhausted
ExhaustCycleWraps around and replays the sequence from the start
ExhaustRepeatLastRepeats the final entry indefinitely
ExhaustServerErrorReturns 500 Internal Server Error

Response values

On and OnRoute accept any value and write it based on its type:

Value typeBehavior
nilAlways writes 204 No Content; the statusCode argument is ignored
stringWritten verbatim as the body
[]byteWritten verbatim as the body
json.RawMessageWritten verbatim as the body
any other valueJSON-encoded into the body; encoding failure yields a 500

Options

Options shape the response and can be passed at three levels: to NewServerBuilder (server level), to On/OnFunc (default handler), or to OnRoute/OnRouteFunc (route level).

OptionPurpose
WithHeaderSets a single response header
WithHeadersAdds multiple response headers from http.Header
WithContentTypeSets the Content-Type header
WithCookieSets a response cookie
WithDelayDelays the response by a fixed duration

Server-level options run on every request before the matched handler, then the handler's own options run. This means route- or handler-level options take precedence over server-level options for the same header.

Example

func TestClient(t *testing.T) {
    ts := httpxtest.NewServerBuilder(t, httpxtest.WithHeader("X-Svr-Lvl", "server-level")).
        On(http.StatusOK, myStruct{Name: "default"}).
        OnRoute(http.MethodGet, "/v1/horton", http.StatusOK, myStruct{Name: "Horton"},
            httpxtest.WithContentType("application/json"),
            httpxtest.WithDelay(10*time.Millisecond)).
        Build()

    result, err := httpx.Get[myStruct](context.Background(), ts.URL+"/v1/horton")
    require.NoError(t, err)
    assert.Equal(t, "Horton", result.Result.Name)
}

To simulate a resource that changes between polls, register a sequence:

func TestPollJob(t *testing.T) {
    ts := httpxtest.NewServerBuilder(t).
        OnRouteSequence(http.MethodGet, "/v1/job", httpxtest.ExhaustRepeatLast,
            httpxtest.Response(http.StatusOK, myStruct{Name: "pending"}),
            httpxtest.Response(http.StatusOK, myStruct{Name: "done"})).
        Build()

    first, err := httpx.Get[myStruct](context.Background(), ts.URL+"/v1/job")
    require.NoError(t, err)
    assert.Equal(t, "pending", first.Result.Name)

    second, err := httpx.Get[myStruct](context.Background(), ts.URL+"/v1/job")
    require.NoError(t, err)
    assert.Equal(t, "done", second.Result.Name)
}

Notes

  • The server is closed automatically via t.Cleanup; you do not need to close it yourself.
  • Use BuildTLS when testing clients that must talk to an HTTPS endpoint.
  • Use On/OnRoute for canned responses and OnFunc/OnRouteFunc when you need custom handler logic.
  • Use OnSequence/OnRouteSequence when successive requests should return different responses in order.
  • Keep options focused so the test setup stays readable.

Examples