httpxtest

package
v1.3.4 Latest Latest
Warning

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

Go to latest
Published: Aug 24, 2026 License: MIT Imports: 11 Imported by: 0

Documentation

Overview

Package httpxtest provides a set of utilities for testing HTTP servers.

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type ExhaustBehavior added in v1.3.0

type ExhaustBehavior int

ExhaustBehavior controls how a sequence of responses is exhausted.

const (
	// ExhaustCycle wraps around to the first entry (default).
	ExhaustCycle ExhaustBehavior = iota

	// ExhaustRepeatLast repeats the final entry indefinitely.
	ExhaustRepeatLast ExhaustBehavior = iota

	// ExhaustServerError returns 500.
	ExhaustServerError
)

type Option

type Option func(w http.ResponseWriter, r *http.Request)

Option is a function that configures how the test server should respond.

func WithContentType

func WithContentType(contentType string) Option

WithContentType sets the Content-Type header on the response.

Example
t := &testing.T{}

ts := httpxtest.NewServerBuilder(t).
	On(http.StatusOK, myStruct{Name: "Horton"}, httpxtest.WithContentType("application/go-snk")).
	Build()

resp, err := httpx.Get[myStruct](context.Background(), ts.URL)
if err != nil {
	panic(err)
}

fmt.Println(resp.Header.Get("Content-Type"))
Output:
application/go-snk

func WithCookie

func WithCookie(name, value string) Option

WithCookie sets a cookie on the response.

Example
t := &testing.T{}

ts := httpxtest.NewServerBuilder(t).
	On(http.StatusOK, myStruct{Name: "Horton"}, httpxtest.WithCookie("session", "abc123")).
	Build()

resp, err := httpx.Get[myStruct](context.Background(), ts.URL)
if err != nil {
	panic(err)
}

fmt.Println(resp.Header.Get("Set-Cookie"))
Output:
session=abc123

func WithDelay

func WithDelay(delay time.Duration) Option

WithDelay adds a delay to the response.

Example
t := &testing.T{}

ts := httpxtest.NewServerBuilder(t).
	On(http.StatusOK, myStruct{Name: "slow"}, httpxtest.WithDelay(10*time.Millisecond)).
	Build()

start := time.Now()

_, err := httpx.Get[myStruct](context.Background(), ts.URL)
if err != nil {
	panic(err)
}

fmt.Println(time.Since(start) >= 10*time.Millisecond)
Output:
true

func WithHeader

func WithHeader(key, value string) Option

WithHeader sets a header on the response.

Example
t := &testing.T{}

ts := httpxtest.NewServerBuilder(t).
	On(http.StatusOK, myStruct{Name: "Horton"}, httpxtest.WithHeader("X-Custom", "go-snk")).
	Build()

resp, err := httpx.Get[myStruct](context.Background(), ts.URL)
if err != nil {
	panic(err)
}

fmt.Println(resp.Header.Get("X-Custom"))
Output:
go-snk

func WithHeaders

func WithHeaders(headers http.Header) Option

WithHeaders sets multiple headers on the response.

Example
t := &testing.T{}

headers := http.Header{}
headers.Set("X-One", "1")
headers.Set("X-Two", "2")

ts := httpxtest.NewServerBuilder(t).
	On(http.StatusOK, myStruct{Name: "Horton"}, httpxtest.WithHeaders(headers)).
	Build()

resp, err := httpx.Get[myStruct](context.Background(), ts.URL)
if err != nil {
	panic(err)
}

fmt.Println(resp.Header.Get("X-One"), resp.Header.Get("X-Two"))
Output:
1 2

func WithJSONContentType added in v1.2.9

func WithJSONContentType() Option

WithJSONContentType sets the Content-Type header to "application/json".

type ResponseFunc added in v1.3.0

type ResponseFunc func(w http.ResponseWriter, r *http.Request)

ResponseFunc is a function that can be used to respond to an HTTP request.

type SequencedResponse added in v1.3.0

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

SequencedResponse is a single entry in a sequence of canned responses.

func Response added in v1.3.0

func Response(statusCode int, body any, options ...Option) SequencedResponse

Response constructs a SequencedResponse for use with OnSequence or OnRouteSequence.

type ServerBuilder

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

ServerBuilder is a builder for HTTP servers.

func NewServerBuilder

func NewServerBuilder(t *testing.T, options ...Option) *ServerBuilder

NewServerBuilder creates a new ServerBuilder.

Example
t := &testing.T{}

// On sets the handler for any request that does not match a route.
ts := httpxtest.NewServerBuilder(t).
	On(http.StatusOK, myStruct{Name: "Horton"}).
	Build()

resp, err := httpx.Get[myStruct](context.Background(), ts.URL)
if err != nil {
	panic(err)
}

fmt.Println(resp.StatusCode, resp.Result.Name)
Output:
200 Horton
Example (ServerLevelOptions)
t := &testing.T{}

// Options passed to NewServerBuilder apply to every response.
ts := httpxtest.NewServerBuilder(t,
	httpxtest.WithHeader("X-Server", "go-snk"),
	httpxtest.WithJSONContentType()).
	OnRoute(http.MethodGet, "/a", http.StatusOK, myStruct{Name: "a"}).
	OnRoute(http.MethodGet, "/b", http.StatusOK, myStruct{Name: "b"}).
	Build()

respA, err := httpx.GetRawResponse(context.Background(), ts.URL+"/a")
if err != nil {
	panic(err)
}

defer func() { _ = respA.Body.Close() }()

respB, err := httpx.GetRawResponse(context.Background(), ts.URL+"/b")
if err != nil {
	panic(err)
}

defer func() { _ = respB.Body.Close() }()

fmt.Println(respA.Header.Get("X-Server"))
fmt.Println(respA.Header.Get("Content-Type"))
Output:
go-snk
application/json

func (*ServerBuilder) Build

func (sb *ServerBuilder) Build() *httptest.Server

Build creates a new HTTP server.

func (*ServerBuilder) BuildTLS

func (sb *ServerBuilder) BuildTLS() *httptest.Server

BuildTLS creates a new HTTPS server.

Example
t := &testing.T{}

// BuildTLS serves over HTTPS. Clients must trust the test certificate;
// here we skip verification.
ts := httpxtest.NewServerBuilder(t).
	On(http.StatusOK, myStruct{Name: "secure"}).
	BuildTLS()

resp, err := httpx.Get[myStruct](context.Background(), ts.URL, httpx.WithInsecureSkipVerify())
if err != nil {
	panic(err)
}

fmt.Println(resp.StatusCode, resp.Result.Name)
Output:
200 secure

func (*ServerBuilder) On

func (sb *ServerBuilder) On(statusCode int, response any, options ...Option) *ServerBuilder

On defines a wildcard path handler. If the response is nil, the server always responds with 204 No Content regardless of statusCode.

Example
package main

import (
	"context"
	"fmt"
	"net/http"
	"testing"

	"github.com/SharkByteSoftware/go-snk/httpx"
	"github.com/SharkByteSoftware/go-snk/httpxtest"
)

func main() {
	t := &testing.T{}

	// A nil response writes 204 No Content with an empty body.
	ts := httpxtest.NewServerBuilder(t).
		On(http.StatusCreated, nil).
		Build()

	resp, err := httpx.GetRawResponse(context.Background(), ts.URL)
	if err != nil {
		panic(err)
	}

	defer func() { _ = resp.Body.Close() }()

	fmt.Println(resp.StatusCode)
}
Output:
204

func (*ServerBuilder) OnFunc

func (sb *ServerBuilder) OnFunc(handler http.HandlerFunc, options ...Option) *ServerBuilder

OnFunc defines a wildcard handler function.

Example
t := &testing.T{}

// OnFunc sets a custom handler for any unmatched request.
ts := httpxtest.NewServerBuilder(t).
	OnFunc(func(w http.ResponseWriter, _ *http.Request) {
		w.WriteHeader(http.StatusOK)
		_, _ = w.Write([]byte(myStructReturn))
	}).
	Build()

resp, err := httpx.Get[myStruct](context.Background(), ts.URL)
if err != nil {
	panic(err)
}

fmt.Println(resp.StatusCode, resp.Result.Name)
Output:
200 test

func (*ServerBuilder) OnRoute

func (sb *ServerBuilder) OnRoute(method string, route string, statusCode int, response any, options ...Option) *ServerBuilder

OnRoute defines a handler for a specific route. If the response is nil, the server always responds with 204 No Content regardless of statusCode.

Example
t := &testing.T{}

// OnRoute responds to a specific method/route; anything else falls
// through to the default handler set with On.
ts := httpxtest.NewServerBuilder(t).
	On(http.StatusOK, myStruct{Name: "default"}).
	OnRoute(http.MethodGet, "/v1/horton", http.StatusOK, myStruct{Name: "Horton"}).
	Build()

matched, err := httpx.Get[myStruct](context.Background(), ts.URL+"/v1/horton")
if err != nil {
	panic(err)
}

unmatched, err := httpx.Get[myStruct](context.Background(), ts.URL+"/other")
if err != nil {
	panic(err)
}

fmt.Println(matched.Result.Name)
fmt.Println(unmatched.Result.Name)
Output:
Horton
default

func (*ServerBuilder) OnRouteFunc

func (sb *ServerBuilder) OnRouteFunc(method string, route string, handler http.HandlerFunc, options ...Option) *ServerBuilder

OnRouteFunc defines a handler for a specific route.

Example
t := &testing.T{}

// OnRouteFunc registers a custom handler for a specific method/route.
ts := httpxtest.NewServerBuilder(t).
	OnRouteFunc(http.MethodPost, "/v1/echo", func(w http.ResponseWriter, r *http.Request) {
		body, _ := io.ReadAll(r.Body)

		w.WriteHeader(http.StatusCreated)
		_, _ = w.Write(body)
	}).
	Build()

resp, err := httpx.Post[myStruct](context.Background(), ts.URL+"/v1/echo", myStruct{Name: "echo"})
if err != nil {
	panic(err)
}

fmt.Println(resp.StatusCode, resp.Result.Name)
Output:
201 echo

func (*ServerBuilder) OnRouteSequence added in v1.3.0

func (sb *ServerBuilder) OnRouteSequence(method, route string, exhaust ExhaustBehavior, responses ...SequencedResponse) *ServerBuilder

OnRouteSequence registers an ordered sequence of responses for a method/route.

Example
t := &testing.T{}

// OnRouteSequence returns each response in order for successive requests to
// a specific method/route. Useful for simulating a resource that changes
// between polls, e.g. a job that transitions from pending to done.
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()

for range 3 {
	resp, err := httpx.Get[myStruct](context.Background(), ts.URL+"/v1/job")
	if err != nil {
		panic(err)
	}

	fmt.Println(resp.Result.Name)
}
Output:
pending
done
done

func (*ServerBuilder) OnSequence added in v1.3.0

func (sb *ServerBuilder) OnSequence(exhaust ExhaustBehavior, responses ...SequencedResponse) *ServerBuilder

OnSequence registers an ordered sequence of responses for the default handler.

Example
t := &testing.T{}

// OnSequence returns each response in order for successive requests to the
// default handler. ExhaustRepeatLast keeps returning the final entry once
// the sequence is exhausted.
ts := httpxtest.NewServerBuilder(t).
	OnSequence(httpxtest.ExhaustRepeatLast,
		httpxtest.Response(http.StatusOK, myStruct{Name: "first"}),
		httpxtest.Response(http.StatusOK, myStruct{Name: "second"}),
	).
	Build()

for range 3 {
	resp, err := httpx.Get[myStruct](context.Background(), ts.URL)
	if err != nil {
		panic(err)
	}

	fmt.Println(resp.Result.Name)
}
Output:
first
second
second
Example (ExhaustCycle)
t := &testing.T{}

// ExhaustCycle wraps back to the first entry after the last one is served.
ts := httpxtest.NewServerBuilder(t).
	OnSequence(httpxtest.ExhaustCycle,
		httpxtest.Response(http.StatusOK, myStruct{Name: "a"}),
		httpxtest.Response(http.StatusOK, myStruct{Name: "b"}),
	).
	Build()

for range 4 {
	resp, err := httpx.Get[myStruct](context.Background(), ts.URL)
	if err != nil {
		panic(err)
	}

	fmt.Println(resp.Result.Name)
}
Output:
a
b
a
b

Jump to

Keyboard shortcuts

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