{"id":15404478,"url":"https://github.com/k1low/httpstub","last_synced_at":"2026-03-02T02:09:44.582Z","repository":{"id":39395844,"uuid":"492741715","full_name":"k1LoW/httpstub","owner":"k1LoW","description":"httpstub provides router ( http.Handler ), server ( *httptest.Server ) and client ( *http.Client ) for stubbing, for testing in Go.","archived":false,"fork":false,"pushed_at":"2025-04-14T13:26:17.000Z","size":217,"stargazers_count":12,"open_issues_count":0,"forks_count":3,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-04-14T14:23:30.423Z","etag":null,"topics":["go","stub","stub-server","testing"],"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/k1LoW.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","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,"zenodo":null},"funding":{"github":"k1LoW","patreon":null,"open_collective":null,"ko_fi":null,"tidelift":null,"community_bridge":null,"liberapay":null,"issuehunt":null,"otechie":null,"custom":null}},"created_at":"2022-05-16T08:12:53.000Z","updated_at":"2025-04-14T13:25:58.000Z","dependencies_parsed_at":"2024-05-13T01:23:49.024Z","dependency_job_id":"567db089-5db5-487a-b743-74c72895e4a4","html_url":"https://github.com/k1LoW/httpstub","commit_stats":{"total_commits":141,"total_committers":4,"mean_commits":35.25,"dds":"0.43262411347517726","last_synced_commit":"5512282a8db317ce652afa966d61fdb3c5b7cd8e"},"previous_names":[],"tags_count":41,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k1LoW%2Fhttpstub","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k1LoW%2Fhttpstub/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k1LoW%2Fhttpstub/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/k1LoW%2Fhttpstub/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/k1LoW","download_url":"https://codeload.github.com/k1LoW/httpstub/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":249275794,"owners_count":21242284,"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":["go","stub","stub-server","testing"],"created_at":"2024-10-01T16:13:06.965Z","updated_at":"2026-03-02T02:09:44.574Z","avatar_url":"https://github.com/k1LoW.png","language":"Go","funding_links":["https://github.com/sponsors/k1LoW"],"categories":[],"sub_categories":[],"readme":"# httpstub [![Go Reference](https://pkg.go.dev/badge/github.com/k1LoW/httpstub.svg)](https://pkg.go.dev/github.com/k1LoW/httpstub) ![Coverage](https://raw.githubusercontent.com/k1LoW/octocovs/main/badges/k1LoW/httpstub/coverage.svg) ![Code to Test Ratio](https://raw.githubusercontent.com/k1LoW/octocovs/main/badges/k1LoW/httpstub/ratio.svg) ![Test Execution Time](https://raw.githubusercontent.com/k1LoW/octocovs/main/badges/k1LoW/httpstub/time.svg)\n\nhttpstub provides router ( `http.Handler` ), server ( `*httptest.Server` ) and client ( `*http.Client` ) for stubbing, for testing in Go.\n\nThere is an gRPC version stubbing tool with the same design concept, [grpcstub](https://github.com/k1LoW/grpcstub).\n\n## Usage\n\n``` go\npackage myapp\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/k1LoW/httpstub\"\n)\n\nfunc TestGet(t *testing.T) {\n\tts := httpstub.NewServer(t)\n\tt.Cleanup(func() {\n\t\tts.Close()\n\t})\n\tts.Method(http.MethodGet).Path(\"/api/v1/users/1\").Header(\"Content-Type\", \"application/json\").ResponseString(http.StatusOK, `{\"name\":\"alice\"}`)\n\n\tres, err := http.Get(ts.URL + \"/api/v1/users/1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Cleanup(func() {\n\t\tres.Body.Close()\n\t})\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgot := string(body)\n\twant := `{\"name\":\"alice\"}`\n\tif got != want {\n\t\tt.Errorf(\"got %v\\nwant %v\", got, want)\n\t}\n\tif len(ts.Requests()) != 1 {\n\t\tt.Errorf(\"got %v\\nwant %v\", len(ts.Requests()), 1)\n\t}\n}\n```\n\nor\n\n``` go\npackage myapp\n\nimport (\n\t\"io\"\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/k1LoW/httpstub\"\n)\n\nfunc TestGet(t *testing.T) {\n\tr := httpstub.NewRouter(t)\n\tr.Method(http.MethodGet).Path(\"/api/v1/users/1\").Header(\"Content-Type\", \"application/json\").ResponseString(http.StatusOK, `{\"name\":\"alice\"}`)\n\tts := r.Server()\n\tt.Cleanup(func() {\n\t\tts.Close()\n\t})\n\n\tres, err := http.Get(ts.URL + \"/api/v1/users/1\")\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tt.Cleanup(func() {\n\t\tres.Body.Close()\n\t})\n\tbody, err := io.ReadAll(res.Body)\n\tif err != nil {\n\t\tt.Fatal(err)\n\t}\n\tgot := string(body)\n\twant := `{\"name\":\"alice\"}`\n\tif got != want {\n\t\tt.Errorf(\"got %v\\nwant %v\", got, want)\n\t}\n\tif len(r.Requests()) != 1 {\n\t\tt.Errorf(\"got %v\\nwant %v\", len(r.Requests()), 1)\n\t}\n}\n```\n\n## Dynamic Response\n\nhttpstub can return responses dynamically using the OpenAPI v3 Document schema.\n\n### Dynamic response to all requests\n\n``` go\nts := httpstub.NewServer(t, httpstub.OpenApi3(\"path/to/schema.yml\"))\nt.Cleanup(func() {\n\tts.Close()\n})\nts.ResponseDynamic()\n```\n\n### Dynamic response to a specific endpoint\n\n``` go\nts := httpstub.NewServer(t, httpstub.OpenApi3(\"path/to/schema.yml\"))\nt.Cleanup(func() {\n\tts.Close()\n})\nts.Method(http.MethodGet).Path(\"/api/v1/users/1\").ResponseDynamic()\n```\n\n### Use specific status code in the response\n\nIt is possible to specify status codes using wildcard.\n\n``` go\nts := httpstub.NewServer(t, httpstub.OpenApi3(\"path/to/schema.yml\"))\nt.Cleanup(func() {\n\tts.Close()\n})\nts.Method(http.MethodPost).Path(\"/api/v1/users\").ResponseDynamic(httpstub.Status(\"2*\"))\n```\n\n### Response modes\n\nhttpstub supports three response modes that control how responses are generated:\n\n- **AlwaysGenerate** (default): Always generates responses from schemas. Examples in the OpenAPI document are ignored.\n- **ExamplesOnly**: Uses only explicit examples from the OpenAPI document. If no example is found, an error is returned.\n- **PreferExamples**: Prefers examples but falls back to schema generation if no example is found.\n\n``` go\n// Use examples only (error if not found)\nts := httpstub.NewServer(t, httpstub.OpenApi3(\"path/to/schema.yml\"), httpstub.DynamicResponseMode(httpstub.ExamplesOnly))\nt.Cleanup(func() {\n\tts.Close()\n})\nts.ResponseDynamic()\n```\n\n``` go\n// Prefer examples, fallback to schema generation\nts := httpstub.NewServer(t, httpstub.OpenApi3(\"path/to/schema.yml\"), httpstub.DynamicResponseMode(httpstub.PreferExamples))\nt.Cleanup(func() {\n\tts.Close()\n})\nts.ResponseDynamic()\n```\n\n### Deterministic response generation\n\nUse the `Seed` option for deterministic response generation.\n\n``` go\nts := httpstub.NewServer(t, httpstub.OpenApi3(\"path/to/schema.yml\"), httpstub.Seed(12345))\nt.Cleanup(func() {\n\tts.Close()\n})\nts.ResponseDynamic()\n```\n\n### HTTP Client that always makes HTTP request to stub server\n\nIt is possible to create a client that will always make an HTTP request to the stub server.\n\n``` go\nts := httpstub.NewServer(t)\nt.Cleanup(func() {\n\tts.Close()\n})\nts.Method(http.MethodGet).Path(\"/api/v1/users/1\").Header(\"Content-Type\", \"application/json\").ResponseString(http.StatusOK, `{\"name\":\"alice\"}`)\ntc := ts.Client()\n\nres, err := tc.Get(\"https://example.com/api/v1/users/1\") // Request goes to stub server instead of https://example.com\nif err != nil {\n\tt.Fatal(err)\n}\n```\n\n## Example\n\n### Stub Twilio\n\n``` go\npackage client_test\n\nimport (\n\t\"net/http\"\n\t\"testing\"\n\n\t\"github.com/k1LoW/httpstub\"\n\ttwilio \"github.com/twilio/twilio-go\"\n\ttwclient \"github.com/twilio/twilio-go/client\"\n\tapi \"github.com/twilio/twilio-go/rest/api/v2010\"\n)\n\nfunc TestTwilioClient(t *testing.T) {\n\tr := httpstub.NewRouter(t)\n\tr.Method(http.MethodPost).Path(\"/2010-04-01/Accounts/*/Messages.json\").ResponseString(http.StatusCreated, `{\"status\":\"sending\"}`)\n\tts := r.Server()\n\tt.Cleanup(func() {\n\t\tts.Close()\n\t})\n\ttc := ts.Client()\n\n\taccountSid := \"ACXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX\"\n\tauthToken := \"YYYYYYYYYYYYYYYYYYYYYYYYYYYYYYYY\"\n\tclient := twilio.NewRestClientWithParams(twilio.ClientParams{\n\t\tClient: \u0026twclient.Client{\n\t\t\tCredentials: twclient.NewCredentials(accountSid, authToken),\n\t\t\tHTTPClient:  tc,\n\t\t},\n\t})\n\tparams := \u0026api.CreateMessageParams{}\n\tparams.SetTo(\"08000000000\")\n\tparams.SetFrom(\"05000000000\")\n\tparams.SetBody(\"Hello there\")\n\tres, err := client.ApiV2010.CreateMessage(params)\n\tif err != nil {\n\t\tt.Error(err)\n\t}\n\n\tgot := res.Status\n\twant := \"sending\"\n\tif *got != want {\n\t\tt.Errorf(\"got %v\\nwant %v\", *got, want)\n\t}\n}\n```\n\n## Alternatives\n\n- [github.com/jharlap/httpstub](https://github.com/jharlap/httpstub): Easy stub HTTP servers for testing in Go\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk1low%2Fhttpstub","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fk1low%2Fhttpstub","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fk1low%2Fhttpstub/lists"}