{"id":17703834,"url":"https://github.com/shawalli/httpmock","last_synced_at":"2025-03-31T04:18:56.343Z","repository":{"id":248616030,"uuid":"829190215","full_name":"shawalli/httpmock","owner":"shawalli","description":"Mock a server in the spirit of testify/mocks.","archived":false,"fork":false,"pushed_at":"2024-12-07T02:26:55.000Z","size":36,"stargazers_count":0,"open_issues_count":2,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-06T08:45:42.115Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"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/shawalli.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,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-07-16T00:14:02.000Z","updated_at":"2024-12-07T02:26:56.000Z","dependencies_parsed_at":"2024-07-16T04:47:14.967Z","dependency_job_id":"944a576f-4950-48e1-b27d-1f4b6f968f4e","html_url":"https://github.com/shawalli/httpmock","commit_stats":null,"previous_names":["shawalli/mockserver"],"tags_count":7,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shawalli%2Fhttpmock","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shawalli%2Fhttpmock/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shawalli%2Fhttpmock/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/shawalli%2Fhttpmock/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/shawalli","download_url":"https://codeload.github.com/shawalli/httpmock/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":246413242,"owners_count":20773054,"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-10-24T21:06:10.187Z","updated_at":"2025-03-31T04:18:56.321Z","avatar_url":"https://github.com/shawalli.png","language":"Go","funding_links":[],"categories":[],"sub_categories":[],"readme":"# httpmock - `testify` for HTTP Requests\n\nThis package provides a mocking interface in the spirit of [`stretchr/testify/mock`](https://github.com/stretchr/testify/tree/master/mock) for HTTP requests.\n\n```go\npackage yours\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\t\"github.com/shawalli/httpmock\"\n\t\"github.com/stretchr/testify/assert\"\n)\n\nfunc TestSomething(t *testing.T) {\n\t// Setup default test server and handler to log requests and return expected responses.\n\t// You may also create your own test server, handler, and mock to manage this.\n\tts := httpmock.NewServer()\n\tdefer ts.Close()\n\n\t// Configure request mocks\n\texpectBearerToken := func(received *http.Request) (output string, differences int) {\n\t\tif _, ok := received.Header[\"Authorization\"]; !ok {\n\t\t\toutput = \"FAIL:  missing header Authorization\"\n\t\t\tdifferences = 1\n\t\t\treturn\n\t\t}\n\t\tval := received.Header.Get(\"Authorization\")\n\t\tif !strings.HasPrefix(val, \"Bearer \") {\n\t\t\toutput = fmt.Sprintf(\"FAIL:  header Authorization: %q != Bearer\", val)\n\t\t\tdifferences = 1\n\t\t\treturn\n\t\t}\n\t\toutput = fmt.Sprintf(\"PASS:  header Authorization: %q == Bearer\", val)\n\t\treturn\n\t}\n\tts.On(http.MethodPatch, \"/foo/1234\", []byte(`{\"bar\": \"baz\"}`)).\n\t\tMatches(expectBearerToken).\n\t\tRespondOK([]byte(`Success!`)).\n\t\tOnce()\n\n\t// Test application code\n\ttc := ts.Client()\n\treq, err := http.NewRequest(\n\t\thttp.MethodPatch,\n\t\tfmt.Sprintf(\"%s/foo/1234\", ts.URL),\n\t\tio.NopCloser(strings.NewReader(`{\"bar\": \"baz\"}`)),\n\t)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to create request! %v\", err)\n\t}\n\treq.Header.Add(\"Authorization\", \"Bearer jkel3450d\")\n\tresp, err := tc.Do(req)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to do request! %v\", err)\n\t}\n\n\t// Assert application expectations\n\tassert.Equal(t, http.StatusOK, resp.StatusCode)\n\trespBody, err := io.ReadAll(resp.Body)\n\tif err != nil {\n\t\tt.Fatalf(\"Failed to read response body! %v\", err)\n\t}\n\tassert.Equal(t, \"Success!\", string(respBody))\n\n\t// Assert httpmock expectations\n\tts.Mock.AssertExpectations(t)\n\tts.Mock.AssertNumberOfRequests(t, http.MethodPatch, \"/foo/1234\", 1)\n}\n```\n\nYou can also use `Mock` directly and implement your own test server. To do so,\nyou should wire up your handler so that the request is passed to\n`Mock.Requested(r)`, and respond using the returned `Response`'s `Write(w)`\nmethod.\n\n## Features\n\n### SafeReadBody\n\n`httpmock.SafeReadBody` will read a `http.Request.Body` and resets the `http.Request.Body` with a fresh `io.Reader` so\nthat subsequent logic may also read the body.\n\n### `httpmock.Mock`\n\n#### On\n\nThe `On()` method is the primary mechanism for configuring mocks. It is primarily designed for `httpmock.Mock`. To support common chaining patterns found in both `httpmock` and `testify/mock`, the `On()` method may also be found on common structs, such as `httpmock.Server` and `httpmock.Response`. In these cases, the `On()` method is just a convenience wrapper to register an expected request against the underlying `httpmock.Mock` object.\n\n```go\nMock.On(http.MethodPost, \"/some/path/1234\").RespondOK()\nServer.On(http.MethodPost, \"/some/path/1234\").RespondOK()\nMock.\n\tOn(http.MethodPost, \"/some/path/1234\").\n\tRespondOK().\n\tOn(http.MethodDelete, \"/some/path/1234\").\n\tRespondNoContent()\n```\n\n#### AnyMethod\n\nUse `httpmock.AnyMethod` to indicate the expected request can contain any valid HTTP method.\n\n```go\nMock.On(httpmock.AnyMethod, \"/some/path\", nil)\n```\n\n#### AnyBody\n\nUse `httpmock.AnyBody` to indicate the expected request can contain any body, or no body at all.\n\n```go\nMock.On(http.MethodPost, \"/some/path/1234\", httpmock.AnyBody)\n```\n\n### `httpmock.Request`\n\n#### Matches\n\nUse `httpmock.Request.Matches()` to perform more complex matching for an expected request. For example, expect that a request should have an Authorization header that uses a bearer token.\n\n```go\nexpectBearerToken := func(received *http.Request) (output string, differences int) {\n\tif _, ok := received.Header[\"Authorization\"]; !ok {\n\t\toutput = \"FAIL:  missing header Authorization\"\n\t\tdifferences = 1\n\t\treturn\n\t}\n\tval := received.Header.Get(\"Authorization\")\n\tif !strings.HasPrefix(val, \"Bearer \") {\n\t\toutput = fmt.Sprintf(\"FAIL:  header Authorization: %q != Bearer\", val)\n\t\tdifferences = 1\n\t\treturn\n\t}\n\toutput = fmt.Sprintf(\"PASS:  header Authorization: %q == Bearer\", val)\n\treturn\n}\nMock.On(http.MethodPost, \"/some/path/1234\", nil).Matches(expectBearerToken)\n```\n\n**On Formatting**: For readability, try to conform to the following pattern when formatting your output:\n\n```\nFAIL:  \u003cactual\u003e != \u003cexpected\u003e\nPASS:  \u003cactual\u003e == \u003cexpected\u003e\n```\n\nThe diff formatting will take care of tabs, newlines, and match-indices for you, so please do not include those formatters.\n\n#### Times, Once, Twice\n\nJust like `testify/mock`, `httpmock` assumes that an expected request may be matched in perpetuity by default. This\nassumption may be altered with the `httpmock.Request.Times()` method. `Times()` takes an integer that indicates the\nnumber of times an expected request should match. After the configured number of times, an expected request will not\nmatch even if it would match otherwise.\n\nAdditionally, two convenience methods are available to simplify common configurations: `Once()` and `Twice()`. They\nbehave as one would expect.\n\n```go\nMock.On(http.MethodDelete, \"/some/path/1234\").Once().RespondNoContent()\nMock.On(http.MethodDelete, \"/some/path/1234\").RespondNoContent().Once()\n```\n\n**Note**: To support chaining, these methods may also be found on the `httpmock.Response` struct as convenience wrappers into the underlying `httpmock.Request` object.\n\n#### Respond, RespondOK, RespondNoContent\n\n`httpmock` provides a basic method to register desired responses to a request with the `httpmock.Request.Respond()`\nmethod. It takes a status code and response body.\n\nAdditionally, two convenience methods are available to simplify common patterns:\n\n- `RespondOK()` - This method responds with a 200 status code and allows for a custom body.\n- `RespondNoContent()` - This responds with a 204 status code and does not take a body, since 204 indicates that the\nresponse contains no content.\n\n```go\nMock.On(http.MethodPost, \"/some/path\", []byte(\"spam\")).RespondOK([]byte(`{\"id\": \"1234\"}`))\nMock.On(http.MethodDelete, \"/some/path/1234\").RespondNoContent()\nMock.On(http.MethodGet, \"/some/path/1234\").Respond(http.StatusNotFound, nil)\nMock.On(http.MethodGet, \"/some/path/1234\").Respond(http.StatusNotFound, []byte(`{\"error\": \"path resource not found\"}`))\n```\n\nIn the future, more convenience methods may be added if they are common, clearly defined, and enhance the readability\nand simplification of the mock response configuration.\n\n#### RespondUsing\n\nIf more complex functionality is needed than `Respond` can provide, `httpmock` allows for custom response\nimplementations with this method. If `RespondUsing` is called, all of the other `Respond` configurations\nare ignored.\n\n```go\n// respWriter calculates the count based on the page and limit and returns these values in the response.\nrespWriter := func(w http.ResponseWriter, r *http.Request) (int, error) {\n\tv := r.URL.Query().Get(\"limit\")\n\n\tlimit := 10\n\tif v != \"\" {\n\t\tlimit, _ = strconv.Atoi(v)\n\t}\n\n\tv = r.URL.Query().Get(\"page\")\n\n\tvar count, page int\n\tif v != \"\" {\n\t\tpage, _ := strconv.Atoi(v)\n\t\tcount = limit * page\n\t}\n\n\tw.WriteHeader(http.StatusOK)\n\treturn w.Write([]byte(`{\"count\": %d, \"page\": %d, \"limit\": %d, \"result\": {...}}`, count, page, limit))\n}\n\nMock.On(http.MethodGet, \"/some/path/1234?page=3\u0026limit=20\", nil).RespondUsing(respWriter)\n```\n\n### `httpmock.Response`\n\n#### Header\n\nUse `httpmock.Response.Header()` to set headers on the response. Multiple values may be passed for the header's value.\nHowever, multiple invocations against the same header will overwrite previous values with the most recent values.\n\n```go\nMock.On(http.MethodGet, \"/some/path\", nil).RespondOK([]byte(`{\"id\": \"1234\"}`)).Header(\"next\", \"abcd\")\n```\n\n### `httpmock.Server`\n\n#### NotRecoverable, IsRecoverable\n\n`httpmock.Server` is a glorified version of `httptest.Server` with a default handler. With both server types, the\nserver runs as a goroutine. The default behavior is to log the panic details and recover from it. However, an\nimplementation can set `NotRecoverable()` to indicate to the default or custom handler that an unmatched request\nshould cause the server to panic outside of the server goroutine and into the main process.\n\nIf writing a custom handler, the handler should react to a panic based on the server's `IsRecoverable()` response.\n\n## Installation\n\nTo install `httpmock`, use `go get`:\n\n```shell\ngo get github.com/shawalli/httpmock\n```\n\n## Troubleshooting\n\n### Nil pointer dereference while reading `http.Request`\n\nIf using `httpmock.Server` or `httptest.Server`, a `http.Request` with no body must use `http.NoBody` instead of\n`nil`. This is due to the fact that the test server is not actually sending and receiving a request, but rather mocking\na request. Using `http.NoBody` indicates to the `net/http` package that the request has no body but is still a valid\n`io.Reader`.\n\n## Todo\n\n- [x] Extend `httptest.Server` to provide a single implementation\n- [x] Request matcher functions\n- ~~[ ] Request URL matcher~~ _(can be implemented with matcher functions feature)_\n- ~~[ ] Request header matching~~ _(can be implemented with matcher functions feature)_\n- [x] Response custom function\n\n## License\n\nThis project is licensed under the terms of the MIT license.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshawalli%2Fhttpmock","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fshawalli%2Fhttpmock","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fshawalli%2Fhttpmock/lists"}