or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

core-assertions.mdformat.mdgbytes.mdgcustom.mdgexec.mdghttp.mdgleak.mdgmeasure.mdgstruct.mdindex.mdmatchers.mdtypes-interfaces.md
tile.json

ghttp.mddocs/

GHTTP Package

The ghttp package provides HTTP server testing utilities for verifying HTTP client behavior. It wraps Go's httptest server with a handler queue system and composable verification handlers for expressive HTTP testing.

Package Information

import "github.com/onsi/gomega/ghttp"

Overview

The ghttp package enables testing HTTP clients by providing a test server that supports registering multiple handlers. Handlers are processed sequentially - the first request goes to the first handler, the second to the second handler, etc. Each handler verifies the incoming request and provides a response.

Server Type

Server

The Server type wraps httptest.Server and manages handler queues and request routing.

type Server struct {
    HTTPTestServer *httptest.Server  // Underlying httptest server
    AllowUnhandledRequests bool      // Allow requests without handlers
    UnhandledRequestStatusCode int   // Status code for unhandled requests
    Writer io.Writer                 // Output writer for logs
}

Server Constructors

// Creates and starts new test HTTP server
func NewServer() *Server

// Creates server without starting it
func NewUnstartedServer() *Server

// Creates and starts new test HTTPS server
func NewTLSServer() *Server

Server Management Methods

// Starts an unstarted server
func (s *Server) Start()

// Closes and cleans up server
func (s *Server) Close()

// Returns server base URL
func (s *Server) URL() string

// Returns server address
func (s *Server) Addr() string

// Implements http.Handler interface
func (s *Server) ServeHTTP(w http.ResponseWriter, req *http.Request)

// Forcibly closes all client connections
func (s *Server) CloseClientConnections()

Handler Management Methods

// Adds handlers to end of queue
func (s *Server) AppendHandlers(handlers ...http.HandlerFunc)

// Sets handler at specific index
func (s *Server) SetHandler(index int, handler http.HandlerFunc)

// Gets handler at specific index
func (s *Server) GetHandler(index int) http.HandlerFunc

// Wraps existing handler with new handler
func (s *Server) WrapHandler(index int, handler http.HandlerFunc)

// Routes method+path to specific handler
func (s *Server) RouteToHandler(method string, path any, handler http.HandlerFunc)

// Resets all handlers and state
func (s *Server) Reset()

The RouteToHandler method accepts either a string or *regexp.Regexp for the path parameter.

Configuration Methods

// Configure unhandled request behavior
func (s *Server) SetAllowUnhandledRequests(bool)
func (s *Server) GetAllowUnhandledRequests() bool

// Set status code for unhandled requests
func (s *Server) SetUnhandledRequestStatusCode(int)
func (s *Server) GetUnhandledRequestStatusCode() int

Request Tracking Methods

// Returns all received requests (both handled and unhandled)
func (s *Server) ReceivedRequests() []*http.Request

RoundTripper

// Creates RoundTripper that routes to this server
func (s *Server) RoundTripper(rt http.RoundTripper) http.RoundTripper

Verification Handlers

Verification handlers check that incoming requests match expected criteria. They can be combined using CombineHandlers.

Handler Combinator

// Combines multiple handlers into one sequential handler
func CombineHandlers(handlers ...http.HandlerFunc) http.HandlerFunc

Request Line Verification

// Verifies HTTP method, path, and optional query string
// path can be string or types.GomegaMatcher
func VerifyRequest(method string, path any, rawQuery ...string) http.HandlerFunc

Header Verification

// Verifies Content-Type header matches exactly
func VerifyContentType(contentType string) http.HandlerFunc

// Verifies MIME type (Content-Type without parameters)
func VerifyMimeType(mimeType string) http.HandlerFunc

// Verifies HTTP Basic Authentication credentials
func VerifyBasicAuth(username string, password string) http.HandlerFunc

// Verifies request contains all specified headers
func VerifyHeader(header http.Header) http.HandlerFunc

// Verifies specific header key has specified values
func VerifyHeaderKV(key string, values ...string) http.HandlerFunc

// Verifies Host header (host can be string or matcher)
func VerifyHost(host any) http.HandlerFunc

Body Verification

// Verifies request body matches byte array
func VerifyBody(expectedBody []byte) http.HandlerFunc

// Verifies request body is equivalent JSON
func VerifyJSON(expectedJSON string) http.HandlerFunc

// Verifies request body JSON matches marshaled object
func VerifyJSONRepresenting(object any) http.HandlerFunc

// Verifies request body is equivalent Protocol Buffer message
func VerifyProtoRepresenting(expected protoiface.MessageV1) http.HandlerFunc

Form Verification

// Verifies form values (url-encoded or multipart)
func VerifyForm(values url.Values) http.HandlerFunc

// Verifies specific form field has specified values
func VerifyFormKV(key string, values ...string) http.HandlerFunc

Response Handlers

Response handlers generate HTTP responses for requests.

Basic Response Handlers

// Responds with status code and body (string or []byte)
func RespondWith(statusCode int, body any, optionalHeader ...http.Header) http.HandlerFunc

// Responds with pointer to status code (allows runtime modification)
func RespondWithPtr(statusCode *int, body any, optionalHeader ...http.Header) http.HandlerFunc

JSON Response Handlers

// Responds with JSON-encoded object
func RespondWithJSONEncoded(statusCode int, object any, optionalHeader ...http.Header) http.HandlerFunc

// JSON response with pointer to status code
func RespondWithJSONEncodedPtr(statusCode *int, object any, optionalHeader ...http.Header) http.HandlerFunc

Protocol Buffer Response Handler

// Responds with Protocol Buffer message
func RespondWithProto(statusCode int, message protoadapt.MessageV1, optionalHeader ...http.Header) http.HandlerFunc

Scoped Handler Factory

The GHTTPWithGomega type allows creating handlers bound to a specific Gomega instance, useful for testing in isolation or with custom failure handlers.

GHTTPWithGomega Type

type GHTTPWithGomega struct {
    // Factory for handlers bound to specific Gomega instance
}

// Creates handler factory with custom Gomega instance
func NewGHTTPWithGomega(gomega Gomega) *GHTTPWithGomega

All verification and response handler functions are available as methods on GHTTPWithGomega:

func (g GHTTPWithGomega) VerifyRequest(method string, path any, rawQuery ...string) http.HandlerFunc
func (g GHTTPWithGomega) VerifyContentType(contentType string) http.HandlerFunc
func (g GHTTPWithGomega) VerifyMimeType(mimeType string) http.HandlerFunc
func (g GHTTPWithGomega) VerifyBasicAuth(username string, password string) http.HandlerFunc
func (g GHTTPWithGomega) VerifyHeader(header http.Header) http.HandlerFunc
func (g GHTTPWithGomega) VerifyHeaderKV(key string, values ...string) http.HandlerFunc
func (g GHTTPWithGomega) VerifyHost(host any) http.HandlerFunc
func (g GHTTPWithGomega) VerifyBody(expectedBody []byte) http.HandlerFunc
func (g GHTTPWithGomega) VerifyJSON(expectedJSON string) http.HandlerFunc
func (g GHTTPWithGomega) VerifyJSONRepresenting(object any) http.HandlerFunc
func (g GHTTPWithGomega) VerifyForm(values url.Values) http.HandlerFunc
func (g GHTTPWithGomega) VerifyFormKV(key string, values ...string) http.HandlerFunc
func (g GHTTPWithGomega) VerifyProtoRepresenting(expected protoiface.MessageV1) http.HandlerFunc
func (g GHTTPWithGomega) RespondWith(statusCode int, body any, optionalHeader ...http.Header) http.HandlerFunc
func (g GHTTPWithGomega) RespondWithPtr(statusCode *int, body any, optionalHeader ...http.Header) http.HandlerFunc
func (g GHTTPWithGomega) RespondWithJSONEncoded(statusCode int, object any, optionalHeader ...http.Header) http.HandlerFunc
func (g GHTTPWithGomega) RespondWithJSONEncodedPtr(statusCode *int, object any, optionalHeader ...http.Header) http.HandlerFunc
func (g GHTTPWithGomega) RespondWithProto(statusCode int, message protoadapt.MessageV1, optionalHeader ...http.Header) http.HandlerFunc

Helper Types

RoundTripperFunc

Function type implementing http.RoundTripper interface:

type RoundTripperFunc func(*http.Request) (*http.Response, error)

func (fn RoundTripperFunc) RoundTrip(r *http.Request) (*http.Response, error)

Usage Examples

Basic Server Setup

import (
    . "github.com/onsi/ginkgo/v2"
    . "github.com/onsi/gomega"
    "github.com/onsi/gomega/ghttp"
)

var _ = Describe("HTTP Client", func() {
    var server *ghttp.Server
    var client *MyHTTPClient

    BeforeEach(func() {
        server = ghttp.NewServer()
        client = NewMyHTTPClient(server.URL())
    })

    AfterEach(func() {
        server.Close()
    })

    It("should make GET request", func() {
        server.AppendHandlers(
            ghttp.CombineHandlers(
                ghttp.VerifyRequest("GET", "/api/users"),
                ghttp.RespondWith(200, `{"users": []}`),
            ),
        )

        users, err := client.GetUsers()
        Expect(err).NotTo(HaveOccurred())
        Expect(users).To(BeEmpty())
    })
})

Testing Authentication

It("should send basic auth credentials", func() {
    server.AppendHandlers(
        ghttp.CombineHandlers(
            ghttp.VerifyRequest("GET", "/protected"),
            ghttp.VerifyBasicAuth("username", "password"),
            ghttp.RespondWith(200, "success"),
        ),
    )

    response, err := client.GetProtectedResource()
    Expect(err).NotTo(HaveOccurred())
    Expect(response).To(Equal("success"))
})

Testing JSON Requests

It("should send JSON payload", func() {
    expectedUser := User{Name: "Alice", Email: "alice@example.com"}

    server.AppendHandlers(
        ghttp.CombineHandlers(
            ghttp.VerifyRequest("POST", "/api/users"),
            ghttp.VerifyJSONRepresenting(expectedUser),
            ghttp.RespondWithJSONEncoded(201, expectedUser),
        ),
    )

    created, err := client.CreateUser("Alice", "alice@example.com")
    Expect(err).NotTo(HaveOccurred())
    Expect(created).To(Equal(expectedUser))
})

Testing Headers

It("should send custom headers", func() {
    server.AppendHandlers(
        ghttp.CombineHandlers(
            ghttp.VerifyRequest("GET", "/api/data"),
            ghttp.VerifyHeader(http.Header{
                "X-API-Key": []string{"secret-key"},
                "Accept":    []string{"application/json"},
            }),
            ghttp.RespondWith(200, `{"data": "value"}`),
        ),
    )

    data, err := client.GetData()
    Expect(err).NotTo(HaveOccurred())
})

Using Pointers for Dynamic Responses

Describe("API endpoint", func() {
    var statusCode int
    var response string

    BeforeEach(func() {
        statusCode = 200
        response = `{"status": "ok"}`

        server.AppendHandlers(
            ghttp.RespondWithPtr(&statusCode, &response),
        )
    })

    Context("when server is healthy", func() {
        It("should return 200", func() {
            result, err := client.HealthCheck()
            Expect(err).NotTo(HaveOccurred())
            Expect(result.Status).To(Equal("ok"))
        })
    })

    Context("when server is unhealthy", func() {
        BeforeEach(func() {
            statusCode = 503
            response = `{"status": "error"}`
        })

        It("should return 503", func() {
            _, err := client.HealthCheck()
            Expect(err).To(HaveOccurred())
        })
    })
})

Route-Based Handlers

BeforeEach(func() {
    // Route always handles GET /health regardless of handler queue
    server.RouteToHandler("GET", "/health",
        ghttp.RespondWith(200, `{"status": "ok"}`),
    )

    // Route with regex pattern
    server.RouteToHandler("GET", regexp.MustCompile(`/api/users/\d+`),
        ghttp.CombineHandlers(
            ghttp.VerifyRequest("GET", MatchRegexp(`/api/users/\d+`)),
            ghttp.RespondWith(200, `{"user": {}}`),
        ),
    )
})

Wrapping Handlers

It("should augment existing handler", func() {
    server.AppendHandlers(
        ghttp.RespondWith(200, "original response"),
    )

    // Add additional verification to existing handler
    server.WrapHandler(0, ghttp.VerifyHeaderKV("X-Custom", "value"))

    // Request must now pass both handlers
    response := client.MakeRequest()
    Expect(response).To(Equal("original response"))
})

Testing Form Submissions

It("should submit form data", func() {
    server.AppendHandlers(
        ghttp.CombineHandlers(
            ghttp.VerifyRequest("POST", "/submit"),
            ghttp.VerifyFormKV("username", "alice"),
            ghttp.VerifyFormKV("password", "secret"),
            ghttp.RespondWith(200, "success"),
        ),
    )

    err := client.Login("alice", "secret")
    Expect(err).NotTo(HaveOccurred())
})

Tracking Received Requests

It("should make multiple requests", func() {
    server.AppendHandlers(
        ghttp.RespondWith(200, "response1"),
        ghttp.RespondWith(200, "response2"),
    )

    client.Request1()
    client.Request2()

    requests := server.ReceivedRequests()
    Expect(requests).To(HaveLen(2))
    Expect(requests[0].URL.Path).To(Equal("/path1"))
    Expect(requests[1].URL.Path).To(Equal("/path2"))
})

Using Custom Gomega Instance

func TestHTTPClient(t *testing.T) {
    g := NewWithT(t)
    ghttp := ghttp.NewGHTTPWithGomega(g)

    server := ghttp.NewServer()
    defer server.Close()

    server.AppendHandlers(
        ghttp.CombineHandlers(
            ghttp.VerifyRequest("GET", "/test"),
            ghttp.RespondWith(200, "ok"),
        ),
    )

    // Test with standard testing.T
}

Allowing Unhandled Requests

BeforeEach(func() {
    server.SetAllowUnhandledRequests(true)
    server.SetUnhandledRequestStatusCode(404)
})

It("should handle unexpected requests gracefully", func() {
    // No handlers registered, but won't fail
    response, err := client.MakeUnexpectedRequest()
    Expect(err).To(HaveOccurred())
    // Server returned 404 for unhandled request
})

Request Handling Order

The server processes requests in the following order:

  1. If the request matches a handler registered with RouteToHandler, that handler is called
  2. Otherwise, if there are handlers registered via AppendHandlers, those handlers are called in order
  3. If all registered handlers have been called:
    • If AllowUnhandledRequests is true, the request receives UnhandledRequestStatusCode
    • If AllowUnhandledRequests is false, the test fails with an error

Best Practices

Use CombineHandlers

Compose verification and response handlers for readability:

server.AppendHandlers(
    ghttp.CombineHandlers(
        ghttp.VerifyRequest("POST", "/api/resource"),
        ghttp.VerifyJSON(`{"name": "test"}`),
        ghttp.VerifyHeader(http.Header{"Authorization": []string{"Bearer token"}}),
        ghttp.RespondWithJSONEncoded(201, createdResource),
    ),
)

Use Pointers for Shared State

Share handler setup across contexts while allowing variation:

var statusCode int
var response MyResponse

BeforeEach(func() {
    statusCode = 200
    response = MyResponse{Status: "ok"}
    server.AppendHandlers(
        ghttp.RespondWithJSONEncodedPtr(&statusCode, &response),
    )
})

// Nested contexts can modify statusCode and response

Use RouteToHandler for Static Endpoints

For endpoints that should always respond the same way (like health checks):

BeforeEach(func() {
    server.RouteToHandler("GET", "/health",
        ghttp.RespondWith(200, "ok"),
    )
})

Track Requests for Complex Scenarios

Use ReceivedRequests() when you need to verify request order or content after the fact:

It("should make requests in order", func() {
    // Set up multiple handlers
    server.AppendHandlers(
        ghttp.RespondWith(200, "1"),
        ghttp.RespondWith(200, "2"),
    )

    client.BatchOperation()

    requests := server.ReceivedRequests()
    Expect(requests).To(HaveLen(2))
    Expect(requests[0].URL.Path).To(Equal("/first"))
    Expect(requests[1].URL.Path).To(Equal("/second"))
})

Clean Up in AfterEach

Always close the server to free resources:

AfterEach(func() {
    server.Close()
})

Integration with Gomega Matchers

Verification handlers that accept matchers (like VerifyRequest and VerifyHost) can use any Gomega matcher:

server.AppendHandlers(
    ghttp.CombineHandlers(
        ghttp.VerifyRequest("GET", ContainSubstring("/api/users")),
        ghttp.VerifyRequest("GET", MatchRegexp(`/api/users/\d+`)),
        ghttp.VerifyHost(HavePrefix("api.")),
        ghttp.RespondWith(200, "ok"),
    ),
)

This allows flexible matching beyond exact string equality.