httpexpect

repository·master·Indexed 25 days ago

https://github.com/gavv/httpexpect

A powerful HTTP assertion library for Go (golang) designed for end-to-end REST API testing. It provides a fluent, declarative API for constructing HTTP requests and validating responses, including status codes, headers, cookies, and complex JSON structures. Key features include recursive payload inspection, JSONPath queries, JSON Schema validation, WebSocket support, and flexible execution via real HTTP clients or net/http and fasthttp handlers.

Tokens
24.3K
Snippets
80
Records
160
Agent score
80%

What's inside httpexpect

  1. Overview of httpexpect

    master

    httpexpect is a declarative, chainable testing library for Go (golang) designed for end-to-end HTTP and REST API testing. It provides builders to construct HTTP requests and assertions to validate HTTP responses and their payloads.

    Key capabilities include:

    • Request Building: Construct URLs with interpolation, manage query parameters, headers, cookies, and various payload types (JSON, urlencoded, multipart, plain text).
    • Response Assertions: Validate status codes, headers, cookies, and payloads (JSON, JSONP, forms, text).
    • Payload Inspection: Recursive inspection of payloads with support for type-specific assertions (object, array, string, number, boolean, null, datetime, duration, cookie), regex, JSONPath queries, and JSON Schema validation.
    • WebSocket Support: Upgrade HTTP connections to WebSockets and interact with WebSocket servers.
    • Flexible Execution: Tests can communicate via a real HTTP client or by invoking net/http or fasthttp handlers directly.
  2. Understand the httpexpect object tree and workflow

    master

    The httpexpect workflow follows a hierarchical object tree where each object is linked via a chain struct. The typical user workflow is:

    1. Create an Expect instance (root object) using httpexpect.Default or httpexpect.WithConfig.
    2. Use Expect methods to create a Request instance (HTTP request builder).
    3. Use Request methods to configure the request (e.g., WithHeader, WithText).
    4. Use Request.Expect() to send the request and receive a Response instance (HTTP response matcher).
    5. Use Response methods to make assertions on the response.
    6. Use Response methods to create child matcher objects for the payload (e.g., Response.Headers() or Response.Body()).
    7. Use methods of matcher objects to make assertions or create nested child matchers.

    Note on Failure Propagation: If an assertion fails, the chain marks that branch as failed. Subsequent assertions on that failed branch (e.g., calling .Body() after a failed .Status() check) are automatically ignored to prevent cascading errors.

    e.GET("/test").Expect().Status(http.StatusOK).Body().IsObject()
  3. Work with JSON responses

    master

    Use .JSON() to access the response body as a JSON object. You can then validate structure using methods like .Object(), .Array(), .ContainsKey(), and .HasValue(). For complex structures, you can use .Path(jsonPath) to run JSONPath queries and iterate over results. You can also decode the JSON directly into a Go struct using .Decode(&target).

    // Validate JSON structure
    obj := e.GET("/fruits/apple").
    	Expect().
    	Status(http.StatusOK).JSON().Object()
    
    obj.Keys().ContainsOnly("colors", "weight")
    obj.Value("colors").Array().ConsistsOf("green", "red")
    
    // Validate using JSONPath
    repos := e.GET("/repos/octocat").
    	Expect().
    	Status(http.StatusOK).JSON()
    
    for _, private := range repos.Path("$..private").Array().Iter() {
    	private.Boolean().IsFalse()
    }
    
    // Decode into a struct
    type User struct {
    	Name   string `json:"name"`
    	Age    int    `json:"age"`
    	Gender string `json:"gender"`
    }
    
    var user User
    e.GET("/user").
    	Expect().
    	Status(http.StatusOK).
    	JSON().
    	Decode(&user)
  4. Quick start with httpexpect

    master

    To get started, create an httpexpect instance using httpexpect.Default(t, baseURL). You can test a local server by using httptest.NewServer and passing its URL to the default constructor. This allows you to chain methods to perform requests and assert expectations on status codes, JSON bodies, and more.

    package example
    
    import (
    	"net/http"
    	"net/http/httptest"
    	"testing"
    
    	"github.com/gavv/httpexpect/v2"
    )
    
    func TestFruits(t *testing.T) {
    	// create http.Handler
    	handler := FruitsHandler()
    
    	// run server using httptest
    	server := httptest.NewServer(handler)
    	defer server.Close()
    
    	// create httpexpect instance
    	e := httpexpect.Default(t, server.URL)
    
    	// is it working?
    	e.GET("/fruits").
    		Expect().
    		Status(http.StatusOK).JSON().Array().IsEmpty()
    }
  5. Format exported function comments

    master

    Exported functions must follow a specific documentation comment format to ensure consistency:

    1. A short function description, indented with one SPACE.
    2. An empty line.
    3. Optional details, indented with one SPACE.
    4. An empty line.
    5. An Example: line, indented with one SPACE.
    6. An empty line.
    7. Example code, indented with one TAB.
    8. No extra empty lines at the end.

    Example of correct formatting:

    // Short function description.
    //
    // Optional details, probably multiple
    // lines or paragraphs.
    //
    // Example:
    //
    //	exampleCode()
    func MyFunction() { ... }
  6. Configure httpexpect with custom Config

    master

    Use httpexpect.WithConfig(httpexpect.Config{...}) to customize the behavior of the client. Key configuration options include:

    • BaseURL: Prepend this URL to all requests.
    • Client: Provide a custom *http.Client (useful for setting timeouts, cookie jars, or TLS config).
    • Reporter: Choose how failures are reported (e.g., NewAssertReporter, NewRequireReporter, NewFatalReporter).
    • Printers: A slice of httpexpect.Printer to output request/response details (e.g., NewDebugPrinter, NewCurlPrinter, NewCompactPrinter).
    • Context: Provide a global context for cancellation/timeouts.
    e := httpexpect.WithConfig(httpexpect.Config{
    	BaseURL: "http://example.com",
    	Client: &http.Client{
    		Jar:     httpexpect.NewCookieJar(),
    		Timeout: time.Second * 30,
    	},
    	Reporter: httpexpect.NewRequireReporter(t),
    	Printers: []httpexpect.Printer{
    		httpexpect.NewDebugPrinter(t, true),
    	},
    })
  7. Initialize a Websocket connection

    master

    To create a new Websocket instance for testing, use NewWebsocketC. You must provide a Config object and an implementation of the WebsocketConn interface.

    Note: NewWebsocket is deprecated; use NewWebsocketC instead.

  8. Use custom templates in DefaultFormatter

    master

    Instead of implementing the Formatter interface from scratch, you can provide custom Go templates to DefaultFormatter. The templates have access to a FormatData object containing all assertion details (test name, path, actual/expected values, diffs, etc.).

    Commonly used template functions include:

    • trim: Trims whitespace.
    • indent: Indents text.
    • wrap: Wraps text to a specific width.
    • join: Joins path tokens with dots.
    • color: Applies color to text.
    • colorjson: Formats and colorizes JSON.
    • colordiff: Colorizes diff output.
    • colorhttp: Colorizes HTTP request/response lines.
  9. Initialize httpexpect with Default()

    master

    Use httpexpect.Default(t, baseURL) for a quick setup. This is a shorthand for WithConfig that uses standard testing defaults:

    • TestName is set to t.Name()
    • Reporter uses NewAssertReporter(t)
    • Printers uses NewCompactPrinter(t)

    t must implement the TestingTB interface (e.g., *testing.T).

    func TestSomething(t *testing.T) {
    	e := httpexpect.Default(t, "http://example.com/")
    
    	e.GET("/path").
    		Expect().
    		Status(http.StatusOK)
    }
  10. Initialize an Object for JSON inspection

    master

    Use NewObject or NewObjectC to create an Object instance for inspecting a map[string]interface{} (the Go representation of a JSON object).

    • NewObject(reporter, value): Creates an object using default configuration. Panics if reporter is nil. Reports failure if value is nil.
    • NewObjectC(config, value): Creates an object using a custom Config.
    object := NewObject(t, map[string]interface{}{"foo": 123})