httpmock

package
v0.0.0-...-b502ad4 Latest Latest
Warning

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

Go to latest
Published: Feb 25, 2025 License: Apache-2.0 Imports: 8 Imported by: 0

Documentation

Overview

Package httpmock provides a simple, declarative API for testing HTTP clients.

The package centers around the Server type, which wraps httptest.Server to provide a way to specify expected HTTP requests and their corresponding responses. This makes it easy to verify that your HTTP client makes the expected calls in the expected order.

Basic usage:

server := httpmock.NewServer(t, []httpmock.Exchange{{
	Request: httpmock.Request{
		Method: "GET",
		Path:   "/api/users",
		Headers: map[string]string{
			"Authorization": "Bearer token123",
		},
	},
	Response: httpmock.Response{
		StatusCode: http.StatusOK,
		Body: map[string]interface{}{
			"users": []string{"alice", "bob"},
		},
	},
}})
defer server.Close()

// Use server.Path("/api/users") as the base URL for your HTTP client...

The Server will verify that requests match the expected method, path, headers, and body (if specified). For bodies, it supports both exact string matches and JSON comparison. You can also provide custom validation functions for more complex request validation:

Request: httpmock.Request{
	Method: "POST",
	Path:   "/api/users",
	Validate: func(r *http.Request) error {
		if r.Header.Get("Content-Length") == "0" {
			return fmt.Errorf("empty request body")
		}
		return nil
	},
}

After your test completes, calling Close will shut down the server and verify that all expected requests were received:

server := httpmock.NewServer(t, []httpmock.Exchange{...})
defer server.Close() // will fail the test if not all expectations were met

You can also verify expectations explicitly at any point using VerifyComplete:

if err := server.VerifyComplete(); err != nil {
	t.Error("not all expected requests were made")
}

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Exchange

type Exchange struct {
	Request  Request
	Response Response
}

Exchange represents a pair of expected HTTP request and corresponding response.

type Request

type Request struct {
	Method  string
	Path    string
	Body    any               // optional: if non-empty, we check the body as JSON (or raw string)
	Headers map[string]string // optional: headers that must be present
	// Validate is an optional callback for additional checks.
	Validate func(r *http.Request) error
}

Request specifies what an incoming HTTP request should look like.

func MergeRequests

func MergeRequests(requests ...Request) Request

MergeRequests creates a new Request by combining multiple requests. Later requests override values from earlier requests. Non-zero/non-empty values from each request override values from previous requests.

type Response

type Response struct {
	StatusCode int               // HTTP status code (defaults to 200 OK if not set)
	Body       any               // can be a JSON-marshalable object or a string
	Headers    map[string]string // optional: headers to include in response
}

Response specifies how to respond to a matched request.

type Server

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

Server provides a declarative API on top of httptest.Server for testing HTTP clients. It allows you to specify a sequence of expected requests and their corresponding responses, making it easy to verify that your HTTP client makes the expected calls in the expected order.

Example (Basic)

1. Examples

testServer := NewServer(&mockT{}, []Exchange{{
	Request: Request{
		Method: "GET",
		Path:   "/hello",
	},
	Response: Response{
		Body: "world",
	},
}})
defer testServer.Close()

resp, _ := http.Get(testServer.Path("/hello"))
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
Output:

world
Example (JsonRequest)
testServer := NewServer(&mockT{}, []Exchange{{
	Request: Request{
		Method: "POST",
		Path:   "/api/users",
		Body:   `{"name":"Alice"}`,
		Headers: map[string]string{
			"Content-Type": "application/json",
		},
	},
	Response: Response{
		StatusCode: http.StatusCreated,
		Body: map[string]interface{}{
			"id":   1,
			"name": "Alice",
		},
	},
}})
defer testServer.Close()

resp, _ := http.Post(testServer.Path("/api/users"),
	"application/json",
	strings.NewReader(`{"name":"Alice"}`))
body, _ := io.ReadAll(resp.Body)
fmt.Println(resp.StatusCode)
fmt.Println(string(body))
Output:

201
{"id":1,"name":"Alice"}
Example (Sequence)
testServer := NewServer(&mockT{}, []Exchange{
	{
		Request: Request{
			Method: "POST",
			Path:   "/login",
			Body:   `{"username":"alice","password":"secret"}`,
		},
		Response: Response{
			Body: map[string]string{"token": "abc123"},
		},
	},
	{
		Request: Request{
			Method: "GET",
			Path:   "/profile",
			Headers: map[string]string{
				"Authorization": "Bearer abc123",
			},
		},
		Response: Response{
			Body: map[string]string{"name": "Alice"},
		},
	},
})
defer testServer.Close()

// Login
resp, _ := http.Post(testServer.Path("/login"),
	"application/json",
	strings.NewReader(`{"username":"alice","password":"secret"}`))
var loginResp struct{ Token string }
err := json.NewDecoder(resp.Body).Decode(&loginResp)
if err != nil {
	fmt.Println("decode error:", err)
	return
}
resp.Body.Close()

// Get profile using token
req, _ := http.NewRequest(http.MethodGet, testServer.Path("/profile"), nil)
req.Header.Set("Authorization", "Bearer "+loginResp.Token)
resp, _ = http.DefaultClient.Do(req)
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
Output:

{"name":"Alice"}
Example (Validation)
testServer := NewServer(&mockT{}, []Exchange{{
	Request: Request{
		Method: "POST",
		Path:   "/upload",
		Validate: func(r *http.Request) error {
			if r.Header.Get("Content-Length") == "0" {
				return fmt.Errorf("empty request body")
			}
			return nil
		},
	},
	Response: Response{
		StatusCode: http.StatusOK,
		Body:       "uploaded",
	},
}})
defer testServer.Close()

// Send non-empty request
resp, _ := http.Post(testServer.Path("/upload"),
	"text/plain",
	strings.NewReader("some data"))
body, _ := io.ReadAll(resp.Body)
fmt.Println(string(body))
Output:

uploaded

func NewServer

func NewServer(t T, expectations []Exchange) *Server

NewServer creates and starts an httptest.Server that will match incoming requests to the provided expectations in order.

func (*Server) BaseURL

func (s *Server) BaseURL() string

BaseURL returns the base URL of the test server.

func (*Server) Close

func (s *Server) Close()

Close shuts down the underlying httptest.Server and verifies all expected exchanges were completed.

func (*Server) Path

func (s *Server) Path(p string) string

Path returns the complete URL for the given path. For example, if the server URL is "http://localhost:12345" and path is "/api/users", this returns "http://localhost:12345/api/users". url.JoinPath handles all path normalization, including handling of leading/trailing slashes.

func (*Server) VerifyComplete

func (s *Server) VerifyComplete() error

VerifyComplete checks that all expected exchanges were completed.

type T

type T interface {
	Errorf(format string, args ...interface{})
	FailNow()
}

T is an interface that captures the testing.T methods we need

Jump to

Keyboard shortcuts

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