httptest

package
v0.0.0-...-fb5e452 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Apr 23, 2026 License: Apache-2.0 Imports: 18 Imported by: 0

Documentation

Overview

Package httptest builds on net/http/httptest to make process-local HTTP requests inside tests as smooth as possible.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Handler

type Handler struct {
	// contains filtered or unexported fields
}

Handler is a wrapper around http.Handler providing convenience methods for use in tests.

func NewHandler

func NewHandler(inner http.Handler) Handler

NewHandler wraps the given http.Handler in type Handler to provide extra convenience methods.

func (Handler) RespondTo

func (h Handler) RespondTo(ctx context.Context, methodAndPath string, options ...RequestOption) Response

RespondTo executes an HTTP request against this handler. The interface is optimized towards readability and brevity in tests for REST APIs:

  • The request method and URL are given in a single string, e.g. "POST /v1/objects/new".
  • Additional headers, a request body, etc. can be provided as a list of options.

There are two main ways to use this function: As a Ginkgo/Gomega user, always check the response with HaveHTTPStatus() first. This will catch any protocol-level and marshaling errors that may occur during the request.

var assets []Asset
resp := h.RespondTo(t.Context(), "GET /v1/assets").CaptureJSON(&assets).Response()
Expect(resp).To(HaveHTTPStatus(http.StatusOK))
Expect(assets).To(HaveLen(4))
Expect(assets[2].Name).To(Equal("baz"))

When not using Gomega, assert on the attributes provided by the Response type. For the most common scenario of a JSON-returning REST API endpoint, the Response.ExpectJSON() method checks the response status and response body in one step:

h.RespondTo(t.Context(), "GET /v1/assets").
	ExpectJSON(t, http.StatusOK, jsonmatch.Array{
		jsonmatch.Object{"name": "foo"},
		jsonmatch.Object{"name": "bar"},
		jsonmatch.Object{"name": "baz"},
		jsonmatch.Object{"name": "qux"},
	})

func (Handler) ServeHTTP

func (h Handler) ServeHTTP(w http.ResponseWriter, r *http.Request)

ServeHTTP implements the http.Handler interface.

type JQModifiableJSONFixture

type JQModifiableJSONFixture struct {
	// contains filtered or unexported fields
}

JQModifiableJSONFixture represents a JSON fixture file, that can be modified with jq syntax before converting it a jsonmatch.Diffable format. We do not save it back to a modified fixture file, so that we circumvent all the possible problems of JSON-fixture files.

func NewJQModifiableJSONFixture

func NewJQModifiableJSONFixture(filePath, testIdentifier string) JQModifiableJSONFixture

NewJQModifiableJSONFixture creates a new JQModifiableJSONFixture for the given fixture file path. The testIdentifier is used in error messages to help identify which test caused a failure.

func (JQModifiableJSONFixture) DiffAgainst

func (j JQModifiableJSONFixture) DiffAgainst(buf []byte) []jsonmatch.Diff

DiffAgainst implements the jsonmatch.Diffable interface.

func (JQModifiableJSONFixture) Modify

func (j JQModifiableJSONFixture) Modify(modifications ...string) JQModifiableJSONFixture

Modify appends one or more jq expressions that will be applied to the fixture's JSON content when converting it to a jsonmatch.Diffable. Multiple modifications are piped together in the order they were added. The receiver is returned to allow method chaining.

type RequestOption

type RequestOption func(*requestParams)

RequestOption controls optional behavior in func Handler.RespondTo().

func MergeRequestOptions

func MergeRequestOptions(options ...RequestOption) RequestOption

MergeRequestOptions fuses multiple RequestOption instances into a single instance. This is useful for constructing higher-level RequestOptions within an application's test suite, to avoid repetition. For example:

// in the test support library
type CustomAPIPayload struct {
	MediaType string
	Contents []byte
}
func WithAPIPayload(p APIPayload) httptest.RequestOption {
	return httptest.MergeRequestOptions(
		httptest.WithHeader("Content-Type", p.MediaType),
		httptest.WithHeader("Content-Range", fmt.Sprintf("bytes 0-%d", len(p.Contents))),
		httptest.WithBody(p.Contents),
	)
}

// within the test function
handler.RespondTo("PUT /payloads/1", WithAPIPayload(p)).
	ExpectStatus(t, http.StatusCreated)

func WithBody

func WithBody(r io.Reader) RequestOption

WithBody adds a request body to an HTTP request.

If the caller does not specify a Content-Type using WithHeader() or WithHeaders(), application/octet-stream will be set.

func WithHeader

func WithHeader(key, value string) RequestOption

WithHeader adds a single HTTP header to an HTTP request.

func WithHeaders

func WithHeaders(hdr http.Header) RequestOption

WithHeaders adds several HTTP headers to an HTTP request.

func WithJSONBody

func WithJSONBody(payload any) RequestOption

WithJSONBody adds a JSON request body to an HTTP request. The provided payload will be serialized into JSON.

If the caller does not specify a Content-Type using WithHeader() or WithHeaders(), application/json will be set.

type Response

type Response struct {
	// contains filtered or unexported fields
}

Response is the result type of Handler.RespondTo(). It provides all components of the generated HTTP response as plain data fields to assert against, as well as convenience methods for complex assertions:

resp := h.RespondTo(t.Context(), "GET /v1/assets")
assert.Equal(t, resp.StatusCode(), http.StatusOK)

Alternatively, the Response() method provides a full *http.Response object, which is useful for Gomega matchers (or when matching more obscure parts of the HTTP response like trailers):

resp := h.RespondTo(t.Context(), "GET /v1/assets").Response()
Expect(resp).To(HaveHTTPStatus(http.StatusOK))

func (Response) BodyBytes

func (r Response) BodyBytes() []byte

BodyBytes returns the response body, or nil if there is no response body.

func (Response) BodyString

func (r Response) BodyString() string

BodyString returns the response body as a string, or the empty string if there is no response body.

The result will be a valid UTF-8-encoded string, with invalid byte sequences replaced as necessary. If this conversion is not desired, use the Body method instead.

func (Response) CaptureHeader

func (r Response) CaptureHeader(key string, target *string) Response

CaptureHeader is a shorthand for capturing a response header. This function returns the Response object unchanged, since it is intended to be written in a chained style. For example:

resp := h.RespondTo(t.Context(), "GET /v1/blobs/"+uuid)
resp.ExpectStatus(t, http.StatusFound)
location := resp.Header().Get("Location")

Can be rephrased as:

var location string
h.RespondTo(t.Context(), "GET /v1/blobs/"+uuid).
	CaptureHeader("Location", &location).
	ExpectStatus(t, http.StatusFound)

This avoids introducing an additional variable holding the Response object, which usually makes the test more readable.

Capturing follows http.Header.Get semantics:

  • If the response does not contain any header with the given key, the empty string will be placed in the target.
  • If the response contains multiple headers with the given key, the first matching header will be placed in the target.

func (Response) CaptureJSON

func (r Response) CaptureJSON(target any) Response

CaptureJSON parses a JSON response body into the given target. Unmarshaling will only take place if the response has a 2xx status code.

Usually, this function returns the Response object unchanged, since it is intended to be written in a chained style:

resp := h.RespondTo(t.Context(), "GET /v1/assets").CaptureJSON(&assets).Response()
Expect(resp).To(HaveHTTPStatus(http.StatusOK))

However, if unmarshaling fails, the response will be updated to have status code 999 and contain the error message as a response body. In the example above, Ginkgo's HaveHTTPStatus assertion will therefore also detect unmarshaling errors that occur during CaptureJSON().

CaptureJSON is usually more ergonomic than ExpectJSON when not asserting on the full response body, but only on specific fields:

var assets []Asset
resp := h.RespondTo(t.Context(), "GET /v1/assets").CaptureJSON(&assets).Response()
Expect(resp).To(HaveHTTPStatus(http.StatusOK))
Expect(assets).To(HaveLen(4))
Expect(assets[2].Name).To(Equal("baz"))

However, when unmarshaling into a type that the implementation also uses, this risks masking marshaling errors that are not visible after a roundtrip, e.g. typos in field names:

type Asset struct {
	Name string `json:"naem"` // the test above does not catch this typo
	// ...
}

This risk can be avoided/reduced by declaring the target type as part of the test:

var assets []struct {
	Name string `json:"name"`
}
resp := h.RespondTo(t.Context(), "GET /v1/assets").CaptureJSON(&assets).Response()
Expect(resp).To(HaveHTTPStatus(http.StatusOK))
Expect(assets).To(HaveLen(4))
Expect(assets[2].Name).To(Equal("baz"))

func (Response) Expect

func (r Response) Expect(assertion func(Response)) Response

Expect allows chaining a custom assertion into Response's chained method style. The following are identical:

resp := h.RespondTo(ctx, "GET /foo/bar")
assertion(resp)
resp.ExpectStatus(t, http.StatusOK)

h.RespondTo(ctx, "GET /foo/bar").
	Expect(assertion).
	ExpectStatus(t, http.StatusOK)

But the second one looks much nicer. This facility is provided for higher-level assertions within an application's test suite, to avoid repetition. For example:

// in the test support library
type CustomAPIPayload struct {
	MediaType string
	Contents []byte
}
func ResponseContainingAPIPayload(t *testing.T, p APIPayload) func(Response) {
	return func(r Response) {
		r.ExpectHeaders(t, http.Header{
			"Content-Length": {strconv.Itoa(len(p.Contents))},
			"Content-Type":   {p.MediaType},
		}).ExpectBody(t, http.StatusOK, p.Contents)
	}
}

// within the test function
handler.RespondTo("GET /payloads/1").
	Expect(ResponseContainingAPIPayload(t, p))

func (Response) ExpectBody

func (r Response) ExpectBody(t assert.TestingT, statusCode int, expectedBody []byte)

ExpectBody asserts that:

  • the status code is equal to the provided value, and
  • the response body matches the provided expected value.

Response body values on either side that are not valid UTF-8 may be mangled when rendering error messages, in order to ensure that those messages are valid UTF-8.

func (Response) ExpectBodyAsInFixture

func (r Response) ExpectBodyAsInFixture(t assert.TestingT, statusCode int, fixturePath string)

ExpectBodyAsInFixture asserts that:

  • the status code is equal to the provided value, and
  • the response body matches the contents of the file at `fixturePath`.

When this function is executed, the actual response body will be written into `fixturePath + ".actual"` as a side effect.

func (Response) ExpectHeader

func (r Response) ExpectHeader(t assert.TestingT, key, expected string) Response

ExpectHeader asserts that the response header in question is set to the provided value.

This function returns the Response object unchanged, since it is intended to be written in a chained style:

h.RespondTo(ctx, "GET /v1/assets").
	ExpectHeader(t, "X-Ratelimit-Action", "asset:list").
	ExpectHeader(t, "X-Ratelimit-Limit", "500").
	ExpectHeader(t, "X-Ratelimit-Remaining", "499").
	ExpectJSON(t, http.StatusOK, jsonmatch.Array{
		jsonmatch.Object{"id": 42, "name": "test_asset"},
	})

func (Response) ExpectHeaders

func (r Response) ExpectHeaders(t assert.TestingT, hdr http.Header) Response

ExpectHeaders asserts that all headers in the provided map are set to the exact same values in the response. To check that a header is absent, include a key with an empty value in the provided map.

This function returns the Response object unchanged, since it is intended to be written in a chained style:

h.RespondTo(ctx, "GET /v1/assets").
	ExpectHeaders(t, http.Header{
		"X-Ratelimit-Action": {"asset:list"},
		"X-Ratelimit-Limit": {"500"},
		"X-Ratelimit-Remaining": {"499"},
		"Unwanted-Header": {}, // check that this header is absent
	}).ExpectJSON(t, http.StatusOK, jsonmatch.Array{
		jsonmatch.Object{"id": 42, "name": "test_asset"},
	})

func (Response) ExpectJSON

func (r Response) ExpectJSON(t assert.TestingT, statusCode int, expected jsonmatch.Diffable)

ExpectJSON asserts that:

  • the status code is equal to the provided value,
  • the response body can be parsed as JSON, and
  • that its contents match the provided jsonmatch literal.

This method is usually more ergonomic than ReceiveJSONInto() when asserting on the entire response body. To capture nondeterministic parts of the response body (e.g. for reuse in later test steps), use the jsonmatch.CaptureField function:

var (
	ctx = t.Context()
	uuid string
)
h.RespondTo(ctx, "POST /v1/assets/new", httptest.WithJSONBody(map[string]any{
	"description": "Test asset",
}).ExpectJSON(t, http.StatusCreated, jsonmatch.Object{
	"description": "Test asset",
	"uuid": jsonmatch.CaptureField(&uuid),
})

h.RespondTo(ctx, "DELETE /v1/assets/"+uuid).
	ExpectStatus(t, http.StatusNoContent)

func (Response) ExpectStatus

func (r Response) ExpectStatus(t assert.TestingT, statusCode int) bool

ExpectStatus asserts that the status code is equal to the provided value. It returns whether the assertion succeeded.

func (Response) ExpectText

func (r Response) ExpectText(t assert.TestingT, statusCode int, expectedBody string)

ExpectText asserts that:

  • the status code is equal to the provided value, and
  • the response body is a valid UTF-8 string matching the provided expected value.

func (Response) Header

func (r Response) Header() http.Header

Header returns the HTTP headers of the response. It is a shorthand for Response().Header().

func (Response) Response

func (r Response) Response() *http.Response

Response returns a handle to the underlying *http.Response object inside this recorded response.

func (Response) StatusCode

func (r Response) StatusCode() int

StatusCode returns the HTTP status code of the response, or 999 for unexpected errors during Handler.RespondTo(). It is a shorthand for Response().StatusCode.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL