{"id":18581900,"url":"https://github.com/coder/hat","last_synced_at":"2025-04-10T11:35:39.225Z","repository":{"id":47283959,"uuid":"127772548","full_name":"coder/hat","owner":"coder","description":"HTTP API testing for Go","archived":false,"fork":false,"pushed_at":"2021-08-23T20:28:08.000Z","size":83,"stargazers_count":39,"open_issues_count":3,"forks_count":4,"subscribers_count":12,"default_branch":"main","last_synced_at":"2025-04-02T22:35:29.556Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":"","language":"Go","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/coder.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2018-04-02T15:09:09.000Z","updated_at":"2025-03-03T05:34:56.000Z","dependencies_parsed_at":"2022-08-12T13:20:48.665Z","dependency_job_id":null,"html_url":"https://github.com/coder/hat","commit_stats":null,"previous_names":["codercom/hat","cdr/hat"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder%2Fhat","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder%2Fhat/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder%2Fhat/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coder%2Fhat/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coder","download_url":"https://codeload.github.com/coder/hat/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248208688,"owners_count":21065205,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":[],"created_at":"2024-11-07T00:08:13.766Z","updated_at":"2025-04-10T11:35:34.196Z","avatar_url":"https://github.com/coder.png","language":"Go","readme":"# hat\n\n[![GoDoc](https://godoc.org/github.com/golang/gddo?status.svg)](https://godoc.org/go.coder.com/hat)\n\nhat is an HTTP API testing framework for Go.\n\nIt's based on composable, reusable response assertions, and request modifiers. It can dramatically **reduce API testing\ncode**, while **improving clarity of test code and test output**. It leans on the standard `net/http` package\nas much as possible.\n\nImport as `go.coder.com/hat`.\n\n## Example\n\nLet's test that twitter is working:\n\n```go\nfunc TestTwitter(tt *testing.T) {\n    t := hat.New(tt, \"https://twitter.com\")\n\n    t.Get(\n        hat.Path(\"/realDonaldTrump\"),\n    ).Send(t).Assert(t,\n        asshat.StatusEqual(http.StatusOK),\n        asshat.BodyMatches(`President`),\n    )\n}\n```\n\u003c!-- START doctoc generated TOC please keep comment here to allow auto update --\u003e\n\u003c!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE --\u003e\n**Table of Contents**  *generated with [DocToc](https://github.com/thlorenz/doctoc)*\n\n- [hat](#hat)\n  - [Example](#example)\n  - [Basic Concepts](#basic-concepts)\n    - [Creating Requests](#creating-requests)\n    - [Sending Requests](#sending-requests)\n    - [Reading Responses](#reading-responses)\n  - [Competitive Comparison](#competitive-comparison)\n    - [API Symbols](#api-symbols)\n    - [LoC](#loc)\n    - [net/http](#nethttp)\n    - [Chaining APIs](#chaining-apis)\n  - [Design Patterns](#design-patterns)\n    - [Format Agnostic](#format-agnostic)\n    - [Minimal API](#minimal-api)\n    - [testing.TB instead of *hat.T](#testingtb-instead-of-hatt)\n\n\u003c!-- END doctoc generated TOC please keep comment here to allow auto update --\u003e\n\n## Basic Concepts\n\n### Creating Requests\nhat's entrypoint is its `New` method\n\n```go\nfunc New(t *testing.T, baseURL string) *T\n```\n\nwhich returns a `hat.T` that embeds a `testing.T`, and provides a bunch of methods such as\n`Get`, `Post`, and `Patch` to generate HTTP requests. Each request method looks like\n\n```go\nfunc (t *T) Get(opts ...RequestOption) Request\n```\n\nRequestOption has the signature\n\n```go\ntype RequestOption func(t testing.TB, req *http.Request)\n```\n\n### Sending Requests\n\nEach request modifies the request however it likes. [A few common `RequestOption`s are provided\nin the `hat` package.](https://godoc.org/go.coder.com/hat#RequestOption)\n\nOnce the request is built, it can be sent\n```go\nfunc (r Request) Send(t *T) *Response\n```\n\nor cloned\n\n```go\nfunc (r Request) Clone(t *T, opts ...RequestOption) Request\n```\n_Cloning is useful when a test is making a slight modification of a complex request._\n\n### Reading Responses\n\nOnce you've sent the request, you're given a `hat.Response`. The `Response` should be asserted.\n\n```go\nfunc (r Response) Assert(t testing.TB, assertions ...ResponseAssertion) Response\n```\n\n`ResponseAssertion` looks like\n\n```go\ntype ResponseAssertion func(t testing.TB, r Response)\n```\n\nA bunch of pre-made response assertions are available in \n[the `asshat` package](https://godoc.org/go.coder.com/hat/asshat).\n\n\n## Competitive Comparison\n\nIt's difficult to say objectively which framework is the best. But, no existing\nframework satisfied us, and we're happy with hat.\n\n| Library                                                    | API Symbols | LoC     | `net/http`               | Custom Assertions/Modifiers |\n|------------------------------------------------------------|-------------|---------|--------------------------|-----------------------------|\n| hat                                                        | **24**      | **410** | :heavy_check_mark:       | :heavy_check_mark:          |\n| [github.com/gavv/httpexpect](//github.com/gavv/httpexpect) | 280         | 10042   | :heavy_multiplication_x: | :warning: (Chaining API)    |\n| [github.com/h2non/baloo](//github.com/h2non/baloo)         | 91          | 2146    | :heavy_multiplication_x: | :warning: (Chaining API)    |\n| [github.com/h2non/gock](//github.com/h2non/gock)           | 122         | 2957    | :heavy_multiplication_x: | :warning: (Chaining API)    |\n\n_LoC was calculated with cloc._\n\n_Will add more columns and libraries on demand._\n\n### API Symbols\n\nSmaller APIs are easier to use and tend to be less opinionated.\n\n### LoC\n\nSmaller codebases have less bugs and are easier to contribute to.\n\n### net/http\n\nWe prefer to use `net/http.Request` and `net/http.Response` so we can reuse the knowledge\nwe already have. Also, we want to reimplement its surface area.\n\n### Chaining APIs\n\nChaining APIs look like\n\n```go\n m.GET(\"/some-path\").\n        Expect().\n        Status(http.StatusOK)\n```\n\nWe dislike them because they make custom assertions and request modifiers a second-class citizen to\nthe assertions and modifiers of the package. This encourages the framework's API to bloat,\nand discourages abstraction on part of the user.\n\n## Design Patterns\n\n### Format Agnostic\n\n`hat` makes no assumption about the structure of your API, request or response encoding, or\nthe size of the requests or responses.\n\n### Minimal API\n\nhat and asshat maintains a very small base of helpers. We think of the provided helpers as primitives\nfor organization and application-specific helpers.\n\n### Always Fatal\n\nWhile some assertions don't invalidate the test, we typically don't mind if they fail the test immediately.\n\nTo avoid the API complexity of selecting\nbetween `Error`s and `Fatal`s, we fatal all the time.\n\n### testing.TB instead of *hat.T\n\nWhen porting your code over to hat, it's better to accept a `testing.TB` than a `*hat.T` or a `*testing.T`.\n\nOnly accept a `*hat.T` when the function is creating additional requests. This makes the code less coupled,\nwhile clarifying the scope of the helper.\n\n---\n\nThis pattern is used in hat itself. The `ResponseAssertion` type and the `Assert` function accept\n`testing.TB` instead of a concrete `*hat.T` or `*testing.T`. At first glance, it seems like wherever\nthe caller is using a `ResponseAssertion` or `Assert`, they would have a `*hat.T`.\n\nIn reality, this choice lets consumers hide the initialization of `hat.T` behind a helper function. E.g:\n\n```go\nfunc TestSomething(t *testing.T) {\n\tmakeRequest(t,\n\t\that.Path(\"/test\"),\n\t).Assert(t,\n\t\tasshat.StatusEqual(t, http.StatusOK),\n\t)\n}\n```","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoder%2Fhat","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoder%2Fhat","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoder%2Fhat/lists"}