or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

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

ghttp.mddocs/

ghttp Package - HTTP Testing Server

The ghttp package provides a fake HTTP server for testing HTTP clients. It allows you to define canned responses, verify that requests match expectations, and simulate various HTTP scenarios in your tests.

Overview

Package: github.com/onsi/gomega/ghttp

The ghttp package is designed for testing HTTP clients and services that make HTTP requests. It provides:

  • Fake HTTP servers that can be started and stopped programmatically
  • Request verification handlers
  • Response generation handlers
  • Support for both HTTP and HTTPS
  • Request history tracking
  • Flexible handler composition

Core Types

Server

{ .api }

type Server struct {
    // Contains filtered or unexported fields
}

A fake HTTP server for testing. The server manages a queue of handlers and automatically verifies that all expected requests are received.

Creating Servers

NewServer

{ .api }

func NewServer() *Server

Creates and starts a new HTTP test server listening on an automatically assigned port.

Returns: *Server - A started HTTP server

Example:

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

NewTLSServer

{ .api }

func NewTLSServer() *Server

Creates and starts a new HTTPS test server with a self-signed certificate.

Returns: *Server - A started HTTPS server

Example:

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

// Use server.HTTPTestServer().Client() to get client with proper TLS config
client := server.HTTPTestServer().Client()

NewUnstartedServer

{ .api }

func NewUnstartedServer() *Server

Creates a new test server but does not start it. You must call Start() manually.

Returns: *Server - An unstarted server

Example:

server := ghttp.NewUnstartedServer()
// Configure server...
server.Start()
defer server.Close()

Server Methods

Start

{ .api }

func (s *Server) Start()

Starts an unstarted server. No-op if server is already started.

Close

{ .api }

func (s *Server) Close()

Closes the server and verifies all expected handlers were called. Fails the test if unexpected requests were received or expected handlers were not invoked.

URL

{ .api }

func (s *Server) URL() string

Returns the full URL of the server including scheme and port.

Returns: Server URL (e.g., "http://127.0.0.1:54321")

Example:

resp, err := http.Get(server.URL() + "/api/users")

Addr

{ .api }

func (s *Server) Addr() string

Returns the address (host:port) of the server.

Returns: Server address (e.g., "127.0.0.1:54321")

HTTPTestServer

{ .api }

func (s *Server) HTTPTestServer() *httptest.Server

Returns the underlying *httptest.Server for advanced configuration.

Returns: The underlying httptest.Server

AppendHandlers

{ .api }

func (s *Server) AppendHandlers(handlers ...http.HandlerFunc)

Appends one or more handlers to the server's handler queue. Handlers are invoked in order as requests arrive.

Parameters:

  • handlers - HTTP handler functions to add to the queue

Example:

server.AppendHandlers(
    ghttp.RespondWith(200, "first response"),
    ghttp.RespondWith(200, "second response"),
)

SetHandler

{ .api }

func (s *Server) SetHandler(index int, handler http.HandlerFunc)

Sets the handler at a specific index in the handler queue.

Parameters:

  • index - Zero-based index of handler to replace
  • handler - New handler function

GetHandler

{ .api }

func (s *Server) GetHandler(index int) http.HandlerFunc

Returns the handler at the specified index.

Parameters:

  • index - Zero-based index of handler

Returns: The handler at the specified index

RouteToHandler

{ .api }

func (s *Server) RouteToHandler(method string, path any, handler http.HandlerFunc)

Routes requests matching the method and path to the specified handler. This bypasses the handler queue.

Parameters:

  • method - HTTP method (GET, POST, etc.)
  • path - Path string or matcher
  • handler - Handler to invoke for matching requests

Example:

server.RouteToHandler("GET", "/health", ghttp.RespondWith(200, "OK"))
server.RouteToHandler("POST", "/api/users", ghttp.RespondWithJSON(201, user))

ReceivedRequests

{ .api }

func (s *Server) ReceivedRequests() []*http.Request

Returns a slice of all requests received by the server.

Returns: Slice of *http.Request

Example:

requests := server.ReceivedRequests()
Expect(requests).To(HaveLen(2))
Expect(requests[0].URL.Path).To(Equal("/api/users"))

Reset

{ .api }

func (s *Server) Reset()

Clears all handlers and request history. Useful for resetting state between tests.

SetUnhandledRequestCallback

{ .api }

func (s *Server) SetUnhandledRequestCallback(callback func(http.ResponseWriter, *http.Request))

Sets a callback function to handle requests that don't match any handler.

Parameters:

  • callback - Function to handle unmatched requests

Example:

server.SetUnhandledRequestCallback(func(w http.ResponseWriter, req *http.Request) {
    w.WriteHeader(404)
    w.Write([]byte("Not Found"))
})

Response Handler Functions

RespondWith

{ .api }

func RespondWith(statusCode int, body any, optionalHeader ...http.Header) http.HandlerFunc

Creates a handler that responds with a fixed status code and body.

Parameters:

  • statusCode - HTTP status code (e.g., 200, 404)
  • body - Response body. Can be string, []byte, or any object (will be JSON-encoded)
  • optionalHeader - Optional HTTP headers to include in response

Returns: http.HandlerFunc

Examples:

// Simple string response
ghttp.RespondWith(200, "OK")

// JSON response from object
ghttp.RespondWith(200, `{"name": "Alice"}`)

// With custom headers
ghttp.RespondWith(200, "OK", http.Header{
    "Content-Type": []string{"text/plain"},
    "X-Custom": []string{"value"},
})

RespondWithPtr

{ .api }

func RespondWithPtr(statusCode *int, body any, optionalHeader ...http.Header) http.HandlerFunc

Like RespondWith, but takes a pointer to status code, allowing the status to be changed dynamically.

Parameters:

  • statusCode - Pointer to HTTP status code
  • body - Response body
  • optionalHeader - Optional headers

Returns: http.HandlerFunc

Example:

code := 200
server.AppendHandlers(ghttp.RespondWithPtr(&code, "response"))

// Later, change the status code
code = 500

RespondWithJSON

{ .api }

func RespondWithJSON(statusCode int, body any, optionalHeader ...http.Header) http.HandlerFunc

Creates a handler that responds with JSON-encoded body and appropriate Content-Type header.

Parameters:

  • statusCode - HTTP status code
  • body - Object to JSON-encode (must be JSON-serializable)
  • optionalHeader - Optional headers (Content-Type will be set to application/json)

Returns: http.HandlerFunc

Example:

user := map[string]string{
    "name": "Alice",
    "email": "alice@example.com",
}
server.AppendHandlers(ghttp.RespondWithJSON(200, user))

Request Verification Handlers

VerifyRequest

{ .api }

func VerifyRequest(method string, path any, rawQuery ...any) http.HandlerFunc

Creates a handler that verifies the request method, path, and optionally query string. Fails the test if verification fails.

Parameters:

  • method - Expected HTTP method (GET, POST, etc.)
  • path - Expected path (string or matcher)
  • rawQuery - Optional query string verification (string, matcher, or nil to skip)

Returns: http.HandlerFunc

Examples:

// Verify method and path
ghttp.VerifyRequest("GET", "/api/users")

// Verify with query parameters
ghttp.VerifyRequest("GET", "/search", "q=golang&page=1")

// Use matcher for path
ghttp.VerifyRequest("GET", MatchRegexp(`/api/users/\d+`))

VerifyJSON

{ .api }

func VerifyJSON(expectedJSON string) http.HandlerFunc

Creates a handler that verifies the request body is semantically equivalent JSON.

Parameters:

  • expectedJSON - Expected JSON string

Returns: http.HandlerFunc

Example:

expectedJSON := `{
    "name": "Alice",
    "age": 30
}`
server.AppendHandlers(
    ghttp.CombineHandlers(
        ghttp.VerifyRequest("POST", "/api/users"),
        ghttp.VerifyJSON(expectedJSON),
        ghttp.RespondWithJSON(201, user),
    ),
)

VerifyBody

{ .api }

func VerifyBody(expectedBody []byte) http.HandlerFunc

Creates a handler that verifies the request body exactly matches the expected bytes.

Parameters:

  • expectedBody - Expected body as byte slice

Returns: http.HandlerFunc

Example:

ghttp.VerifyBody([]byte("exact body content"))

VerifyHeader

{ .api }

func VerifyHeader(header http.Header) http.HandlerFunc

Creates a handler that verifies the request contains the specified headers.

Parameters:

  • header - Expected headers (all must be present with exact values)

Returns: http.HandlerFunc

Example:

expectedHeaders := http.Header{
    "Content-Type": []string{"application/json"},
    "Authorization": []string{"Bearer token123"},
}
ghttp.VerifyHeader(expectedHeaders)

VerifyHeaderKV

{ .api }

func VerifyHeaderKV(key string, values ...string) http.HandlerFunc

Creates a handler that verifies the request contains a specific header key with the specified values.

Parameters:

  • key - Header name
  • values - Expected header values (variadic)

Returns: http.HandlerFunc

Example:

ghttp.VerifyHeaderKV("Content-Type", "application/json")
ghttp.VerifyHeaderKV("Accept", "application/json", "text/plain")

VerifyHost

{ .api }

func VerifyHost(host any) http.HandlerFunc

Creates a handler that verifies the request Host header matches the expected value.

Parameters:

  • host - Expected host (string or matcher)

Returns: http.HandlerFunc

Example:

ghttp.VerifyHost("api.example.com")
ghttp.VerifyHost(MatchRegexp(`.*\.example\.com`))

VerifyContentType

{ .api }

func VerifyContentType(contentType string) http.HandlerFunc

Creates a handler that verifies the Content-Type header.

Parameters:

  • contentType - Expected Content-Type value

Returns: http.HandlerFunc

Example:

ghttp.VerifyContentType("application/json")

VerifyMimeType

{ .api }

func VerifyMimeType(mimeType string) http.HandlerFunc

Creates a handler that verifies the MIME type portion of the Content-Type header (ignoring charset and other parameters).

Parameters:

  • mimeType - Expected MIME type

Returns: http.HandlerFunc

Example:

ghttp.VerifyMimeType("application/json")
// Matches "application/json; charset=utf-8"

VerifyJSONRepresenting

{ .api }

func VerifyJSONRepresenting(object any) http.HandlerFunc

Creates a handler that verifies the request body is JSON that marshals to match the provided object.

Parameters:

  • object - Object to compare against

Returns: http.HandlerFunc

Example:

expected := User{Name: "Alice", Age: 30}
ghttp.VerifyJSONRepresenting(expected)

VerifyForm

{ .api }

func VerifyForm(values url.Values) http.HandlerFunc

Creates a handler that verifies the request contains the specified form values.

Parameters:

  • values - Expected form values

Returns: http.HandlerFunc

Example:

expected := url.Values{
    "username": []string{"alice"},
    "password": []string{"secret"},
}
ghttp.VerifyForm(expected)

VerifyFormKV

{ .api }

func VerifyFormKV(key string, values ...string) http.HandlerFunc

Creates a handler that verifies the request contains a specific form key with the specified values.

Parameters:

  • key - Form field name
  • values - Expected form field values (variadic)

Returns: http.HandlerFunc

Example:

ghttp.VerifyFormKV("username", "alice")
ghttp.VerifyFormKV("roles", "admin", "user")

VerifyBasicAuth

{ .api }

func VerifyBasicAuth(username string, password string) http.HandlerFunc

Creates a handler that verifies HTTP Basic Authentication credentials.

Parameters:

  • username - Expected username
  • password - Expected password

Returns: http.HandlerFunc

Example:

ghttp.VerifyBasicAuth("admin", "secret123")

Handler Composition

CombineHandlers

{ .api }

func CombineHandlers(handlers ...http.HandlerFunc) http.HandlerFunc

Combines multiple handlers into a single handler. All handlers are invoked in sequence.

Parameters:

  • handlers - Handlers to combine

Returns: http.HandlerFunc that executes all handlers

Example:

handler := ghttp.CombineHandlers(
    ghttp.VerifyRequest("POST", "/api/users"),
    ghttp.VerifyJSON(`{"name": "Alice"}`),
    ghttp.VerifyContentType("application/json"),
    ghttp.RespondWithJSON(201, map[string]string{
        "id": "123",
        "name": "Alice",
    }),
)
server.AppendHandlers(handler)

Usage Examples

Basic GET Request Testing

package client_test

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

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

    BeforeEach(func() {
        server = ghttp.NewServer()
    })

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

    It("should fetch user data", func() {
        server.AppendHandlers(
            ghttp.CombineHandlers(
                ghttp.VerifyRequest("GET", "/api/users/123"),
                ghttp.RespondWithJSON(200, map[string]interface{}{
                    "id": "123",
                    "name": "Alice",
                }),
            ),
        )

        client := NewClient(server.URL())
        user, err := client.GetUser("123")

        Expect(err).NotTo(HaveOccurred())
        Expect(user.Name).To(Equal("Alice"))
        Expect(server.ReceivedRequests()).To(HaveLen(1))
    })
})

Testing POST Requests with JSON

It("should create a user", func() {
    expectedRequest := `{
        "name": "Bob",
        "email": "bob@example.com"
    }`

    expectedResponse := map[string]interface{}{
        "id": "456",
        "name": "Bob",
        "email": "bob@example.com",
    }

    server.AppendHandlers(
        ghttp.CombineHandlers(
            ghttp.VerifyRequest("POST", "/api/users"),
            ghttp.VerifyJSON(expectedRequest),
            ghttp.VerifyContentType("application/json"),
            ghttp.RespondWithJSON(201, expectedResponse),
        ),
    )

    client := NewClient(server.URL())
    user, err := client.CreateUser("Bob", "bob@example.com")

    Expect(err).NotTo(HaveOccurred())
    Expect(user.ID).To(Equal("456"))
})

Testing Authentication

It("should authenticate with bearer token", func() {
    server.AppendHandlers(
        ghttp.CombineHandlers(
            ghttp.VerifyRequest("GET", "/api/protected"),
            ghttp.VerifyHeaderKV("Authorization", "Bearer secret-token"),
            ghttp.RespondWith(200, "Protected resource"),
        ),
    )

    client := NewClient(server.URL())
    client.SetToken("secret-token")

    data, err := client.GetProtectedResource()
    Expect(err).NotTo(HaveOccurred())
    Expect(data).To(Equal("Protected resource"))
})

It("should authenticate with basic auth", func() {
    server.AppendHandlers(
        ghttp.CombineHandlers(
            ghttp.VerifyRequest("GET", "/api/admin"),
            ghttp.VerifyBasicAuth("admin", "password123"),
            ghttp.RespondWith(200, "Admin data"),
        ),
    )

    client := NewClient(server.URL())
    client.SetBasicAuth("admin", "password123")

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

Testing Multiple Requests

It("should handle multiple sequential requests", func() {
    server.AppendHandlers(
        ghttp.RespondWithJSON(200, map[string]string{"page": "1"}),
        ghttp.RespondWithJSON(200, map[string]string{"page": "2"}),
        ghttp.RespondWithJSON(200, map[string]string{"page": "3"}),
    )

    client := NewClient(server.URL())

    page1, _ := client.GetPage(1)
    page2, _ := client.GetPage(2)
    page3, _ := client.GetPage(3)

    Expect(page1["page"]).To(Equal("1"))
    Expect(page2["page"]).To(Equal("2"))
    Expect(page3["page"]).To(Equal("3"))
    Expect(server.ReceivedRequests()).To(HaveLen(3))
})

Testing Query Parameters

It("should verify query parameters", func() {
    server.AppendHandlers(
        ghttp.CombineHandlers(
            ghttp.VerifyRequest("GET", "/api/search", "q=golang&limit=10"),
            ghttp.RespondWithJSON(200, []string{"result1", "result2"}),
        ),
    )

    client := NewClient(server.URL())
    results, err := client.Search("golang", 10)

    Expect(err).NotTo(HaveOccurred())
    Expect(results).To(HaveLen(2))
})

Testing Error Responses

It("should handle 404 errors", func() {
    server.AppendHandlers(
        ghttp.CombineHandlers(
            ghttp.VerifyRequest("GET", "/api/users/999"),
            ghttp.RespondWith(404, `{"error": "User not found"}`),
        ),
    )

    client := NewClient(server.URL())
    _, err := client.GetUser("999")

    Expect(err).To(HaveOccurred())
    Expect(err.Error()).To(ContainSubstring("not found"))
})

It("should handle 500 errors", func() {
    server.AppendHandlers(
        ghttp.RespondWith(500, "Internal Server Error"),
    )

    client := NewClient(server.URL())
    _, err := client.GetUser("123")

    Expect(err).To(HaveOccurred())
})

Using Routing for Flexible Handlers

BeforeEach(func() {
    server = ghttp.NewServer()

    // Set up routes that persist across requests
    server.RouteToHandler("GET", "/health",
        ghttp.RespondWith(200, "OK"),
    )

    server.RouteToHandler("GET", "/api/users",
        ghttp.RespondWithJSON(200, []map[string]string{
            {"id": "1", "name": "Alice"},
            {"id": "2", "name": "Bob"},
        }),
    )
})

It("can call health endpoint multiple times", func() {
    client := NewClient(server.URL())

    // Can call multiple times without adding more handlers
    Expect(client.CheckHealth()).To(BeTrue())
    Expect(client.CheckHealth()).To(BeTrue())
    Expect(client.CheckHealth()).To(BeTrue())
})

Testing HTTPS Clients

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

    BeforeEach(func() {
        server = ghttp.NewTLSServer()
    })

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

    It("should work over HTTPS", func() {
        server.AppendHandlers(
            ghttp.RespondWith(200, "secure response"),
        )

        // Use the test server's client which trusts the self-signed cert
        client := server.HTTPTestServer().Client()
        resp, err := client.Get(server.URL() + "/api/data")

        Expect(err).NotTo(HaveOccurred())
        defer resp.Body.Close()

        body, _ := io.ReadAll(resp.Body)
        Expect(string(body)).To(Equal("secure response"))
    })
})

Testing with Custom Headers

It("should include custom headers", func() {
    server.AppendHandlers(
        ghttp.CombineHandlers(
            ghttp.VerifyRequest("GET", "/api/data"),
            ghttp.VerifyHeaderKV("X-Request-ID", "12345"),
            ghttp.VerifyHeaderKV("X-Client-Version", "1.0.0"),
            ghttp.RespondWith(200, "data", http.Header{
                "X-Response-ID": []string{"67890"},
            }),
        ),
    )

    client := NewClient(server.URL())
    client.SetHeader("X-Request-ID", "12345")
    client.SetHeader("X-Client-Version", "1.0.0")

    resp, err := client.GetData()
    Expect(err).NotTo(HaveOccurred())
    Expect(resp.Header.Get("X-Response-ID")).To(Equal("67890"))
})

Testing Request Body with Matchers

It("should verify request body with matchers", func() {
    server.AppendHandlers(
        ghttp.CombineHandlers(
            ghttp.VerifyRequest("POST", "/api/logs"),
            func(w http.ResponseWriter, req *http.Request) {
                body, _ := io.ReadAll(req.Body)
                Expect(string(body)).To(ContainSubstring("error"))
                Expect(string(body)).To(ContainSubstring("timestamp"))
            },
            ghttp.RespondWith(201, "Log created"),
        ),
    )

    client := NewClient(server.URL())
    err := client.SendLog("error", time.Now())
    Expect(err).NotTo(HaveOccurred())
})

Simulating Timeouts and Slow Responses

It("should timeout on slow responses", func() {
    server.AppendHandlers(
        func(w http.ResponseWriter, req *http.Request) {
            time.Sleep(5 * time.Second)
            w.WriteHeader(200)
        },
    )

    client := NewClient(server.URL())
    client.SetTimeout(1 * time.Second)

    _, err := client.GetData()
    Expect(err).To(HaveOccurred())
    Expect(err.Error()).To(ContainSubstring("timeout"))
})

Verifying Request History

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

    client := NewClient(server.URL())
    client.Get("/endpoint1")
    client.Get("/endpoint2")

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

Dynamically Changing Responses

It("should allow dynamic response modification", func() {
    statusCode := 200

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

    client := NewClient(server.URL())

    // First request succeeds
    _, err := client.Get("/data")
    Expect(err).NotTo(HaveOccurred())

    // Change status code
    statusCode = 503

    // Second request fails
    _, err = client.Get("/data")
    Expect(err).To(HaveOccurred())
})

Best Practices

  1. Always Close Servers: Use defer server.Close() in AfterEach to ensure proper cleanup and verification.

  2. Verify Requests: Use verification handlers to ensure your client sends correct requests.

  3. Test Both Success and Failure: Simulate error responses to test error handling.

  4. Use CombineHandlers: Combine verification and response handlers for comprehensive testing.

  5. Check Request History: Use ReceivedRequests() to verify the number and order of requests.

  6. Use RouteToHandler for Repeated Endpoints: For endpoints called multiple times, use routing instead of queue-based handlers.

  7. Test HTTPS When Needed: Use NewTLSServer() and the test server's client for HTTPS testing.

  8. Reset Between Tests: Call server.Reset() if reusing a server across multiple test cases.

Common Patterns

RESTful API Testing Suite

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

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

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

    Describe("User Management", func() {
        It("lists users", func() {
            server.AppendHandlers(
                ghttp.CombineHandlers(
                    ghttp.VerifyRequest("GET", "/api/users"),
                    ghttp.RespondWithJSON(200, []User{{ID: "1", Name: "Alice"}}),
                ),
            )

            users, err := client.ListUsers()
            Expect(err).NotTo(HaveOccurred())
            Expect(users).To(HaveLen(1))
        })

        It("creates a user", func() {
            server.AppendHandlers(
                ghttp.CombineHandlers(
                    ghttp.VerifyRequest("POST", "/api/users"),
                    ghttp.VerifyJSON(`{"name":"Bob"}`),
                    ghttp.RespondWithJSON(201, User{ID: "2", Name: "Bob"}),
                ),
            )

            user, err := client.CreateUser("Bob")
            Expect(err).NotTo(HaveOccurred())
            Expect(user.ID).To(Equal("2"))
        })
    })
})

This comprehensive approach enables thorough testing of HTTP clients with minimal boilerplate and maximum flexibility.