hat

package module
v0.0.0-...-222976f Latest Latest
Warning

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

Go to latest
Published: Aug 23, 2021 License: MIT Imports: 11 Imported by: 0

README

hat

GoDoc

hat is an HTTP API testing framework for Go.

It's based on composable, reusable response assertions, and request modifiers. It can dramatically reduce API testing code, while improving clarity of test code and test output. It leans on the standard net/http package as much as possible.

Import as go.coder.com/hat.

Example

Let's test that twitter is working:

func TestTwitter(tt *testing.T) {
    t := hat.New(tt, "https://twitter.com")

    t.Get(
        hat.Path("/realDonaldTrump"),
    ).Send(t).Assert(t,
        asshat.StatusEqual(http.StatusOK),
        asshat.BodyMatches(`President`),
    )
}

Table of Contents generated with DocToc

Basic Concepts

Creating Requests

hat's entrypoint is its New method

func New(t *testing.T, baseURL string) *T

which returns a hat.T that embeds a testing.T, and provides a bunch of methods such as Get, Post, and Patch to generate HTTP requests. Each request method looks like

func (t *T) Get(opts ...RequestOption) Request

RequestOption has the signature

type RequestOption func(t testing.TB, req *http.Request)
Sending Requests

Each request modifies the request however it likes. A few common RequestOptions are provided in the hat package.

Once the request is built, it can be sent

func (r Request) Send(t *T) *Response

or cloned

func (r Request) Clone(t *T, opts ...RequestOption) Request

Cloning is useful when a test is making a slight modification of a complex request.

Reading Responses

Once you've sent the request, you're given a hat.Response. The Response should be asserted.

func (r Response) Assert(t testing.TB, assertions ...ResponseAssertion) Response

ResponseAssertion looks like

type ResponseAssertion func(t testing.TB, r Response)

A bunch of pre-made response assertions are available in the asshat package.

Competitive Comparison

It's difficult to say objectively which framework is the best. But, no existing framework satisfied us, and we're happy with hat.

Library API Symbols LoC net/http Custom Assertions/Modifiers
hat 24 410
github.com/gavv/httpexpect 280 10042 ⚠ (Chaining API)
github.com/h2non/baloo 91 2146 ⚠ (Chaining API)
github.com/h2non/gock 122 2957 ⚠ (Chaining API)

LoC was calculated with cloc.

Will add more columns and libraries on demand.

API Symbols

Smaller APIs are easier to use and tend to be less opinionated.

LoC

Smaller codebases have less bugs and are easier to contribute to.

net/http

We prefer to use net/http.Request and net/http.Response so we can reuse the knowledge we already have. Also, we want to reimplement its surface area.

Chaining APIs

Chaining APIs look like

 m.GET("/some-path").
        Expect().
        Status(http.StatusOK)

We dislike them because they make custom assertions and request modifiers a second-class citizen to the assertions and modifiers of the package. This encourages the framework's API to bloat, and discourages abstraction on part of the user.

Design Patterns

Format Agnostic

hat makes no assumption about the structure of your API, request or response encoding, or the size of the requests or responses.

Minimal API

hat and asshat maintains a very small base of helpers. We think of the provided helpers as primitives for organization and application-specific helpers.

Always Fatal

While some assertions don't invalidate the test, we typically don't mind if they fail the test immediately.

To avoid the API complexity of selecting between Errors and Fatals, we fatal all the time.

testing.TB instead of *hat.T

When porting your code over to hat, it's better to accept a testing.TB than a *hat.T or a *testing.T.

Only accept a *hat.T when the function is creating additional requests. This makes the code less coupled, while clarifying the scope of the helper.


This pattern is used in hat itself. The ResponseAssertion type and the Assert function accept testing.TB instead of a concrete *hat.T or *testing.T. At first glance, it seems like wherever the caller is using a ResponseAssertion or Assert, they would have a *hat.T.

In reality, this choice lets consumers hide the initialization of hat.T behind a helper function. E.g:

func TestSomething(t *testing.T) {
	makeRequest(t,
		hat.Path("/test"),
	).Assert(t,
		asshat.StatusEqual(t, http.StatusOK),
	)
}

Documentation

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

This section is empty.

Types

type Request

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

Request represents a pending HTTP request.

func (Request) Clone

func (r Request) Clone(t *T, opts ...RequestOption) Request

Clone creates a duplicate HTTP request and applies opts to it.

func (Request) Send

func (r Request) Send(t *T) *Response

Send dispatches the HTTP request.

type RequestOption

type RequestOption func(t testing.TB, req *http.Request)

RequestOption modifies a request. Use the passed t to fail if the option cannot be set.

func Body

func Body(r io.Reader) RequestOption

Body sets the body of a request.

func CombineRequestOptions

func CombineRequestOptions(opts ...RequestOption) RequestOption

CombineRequestOptions returns a new RequestOption which internally calls each member of options in the provided order.

func Header(key, value string) RequestOption

Header sets a header on the request.

func Path

func Path(elem string) RequestOption

Path joins elem on to the URL.

func URLParams

func URLParams(v url.Values) RequestOption

URLParams sets the URL parameters of the request.

type Response

type Response struct {
	*http.Response
}

Response represents an HTTP response generated by hat.Request.

func (Response) Assert

func (r Response) Assert(t testing.TB, assertions ...ResponseAssertion) Response

Assert runs each requireion against the response. It closes the response body after all of the requireions have ran. Assert must be called for every response as it will ensure the body is closed. If you want to continue to reuse the connection, you must read the response body.

func (Response) DuplicateBody

func (r Response) DuplicateBody(t testing.TB) []byte

DuplicateBody reads in the response body. It replaces the underlying body with a duplicate.

type ResponseAssertion

type ResponseAssertion func(t testing.TB, r Response)

ResponseAssertion requires a quality of the response.

func CombineResponseAssertions

func CombineResponseAssertions(as ...ResponseAssertion) ResponseAssertion

CombineResponseAssertions returns a new ResponseAssertion which internally calls each member of requires in the provided order.

type T

type T struct {
	*testing.T

	URL    *url.URL
	Client *http.Client
	// contains filtered or unexported fields
}

T represents a test instance. It intentionally does not provide any default request modifiers or default response requireions. Defaults should be explicitly provided to Request and Assert.

func New

func New(t *testing.T, baseURL string) *T

New creates a *T from a *testing.T.

func (*T) AddPersistentOpts

func (t *T) AddPersistentOpts(opts ...RequestOption)

func (*T) Delete

func (t *T) Delete(opts ...RequestOption) Request

func (*T) Get

func (t *T) Get(opts ...RequestOption) Request

func (*T) Head

func (t *T) Head(opts ...RequestOption) Request

func (*T) Patch

func (t *T) Patch(opts ...RequestOption) Request

func (*T) Post

func (t *T) Post(opts ...RequestOption) Request

func (*T) Put

func (t *T) Put(opts ...RequestOption) Request

func (T) Request

func (t T) Request(method string, opts ...RequestOption) Request

Request creates an HTTP request to the endpoint.

func (*T) Run

func (t *T) Run(name string, fn func(t *T))

Run creates a subtest. The subtest inherits the settings of T.

func (*T) RunPath

func (t *T) RunPath(elem string, fn func(t *T))

RunPath creates a subtest with segment appended to the internal URL. It uses segment as the name of the subtest.

Directories

Path Synopsis
Package asshat provides common ResponseAssertions.
Package asshat provides common ResponseAssertions.
examples

Jump to

Keyboard shortcuts

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