1# httpmock [](https://github.com/jarcoal/httpmock/actions?query=workflow%3ABuild) [](https://coveralls.io/github/jarcoal/httpmock?branch=v1) [](https://godoc.org/github.com/jarcoal/httpmock) [](https://github.com/jarcoal/httpmock/releases) [](https://github.com/avelino/awesome-go/#testing)
2
3Easy mocking of http responses from external resources.
4
5## Install
6
7Currently supports Go 1.9 - 1.18.
8
9`v1` branch has to be used instead of `master`.
10
11
12### Using go modules (aka. `go mod`)
13
14In your go files, simply use:
15```go
16import "github.com/jarcoal/httpmock"
17```
18
19Then next `go mod tidy` or `go test` invocation will automatically
20populate your `go.mod` with the last httpmock release, now
21[](https://github.com/jarcoal/httpmock/releases).
22
23Note you can use `go mod vendor` to vendor your dependencies.
24
25
26### Using `$GOPATH`
27
28`v1` branch is configured as the default branch in github, so:
29```
30go get github.com/jarcoal/httpmock
31```
32
33automatically downloads the `v1` branch in `$GOPATH/src`. Then in your
34go files use:
35```go
36import "github.com/jarcoal/httpmock"
37```
38
39
40### Vendoring, using [`govendor`](https://github.com/kardianos/govendor) for example
41
42When vendoring is used, `v1` branch has to be specified. Two choices here:
43
44- preferred way:
45 ```
46 govendor fetch github.com/jarcoal/httpmock@v1
47 ```
48 then in go files:
49 ```go
50 import "github.com/jarcoal/httpmock"
51 ```
52- old way (before `v1` was set as default branch), use gopkg to read from
53 `v1` branch:
54 ```
55 govendor fetch gopkg.in/jarcoal/httpmock.v1
56 ```
57 then in go files:
58 ```go
59 import "gopkg.in/jarcoal/httpmock.v1"
60 ```
61
62
63## Usage
64
65### Simple Example:
66```go
67func TestFetchArticles(t *testing.T) {
68 httpmock.Activate()
69 defer httpmock.DeactivateAndReset()
70
71 // Exact URL match
72 httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles",
73 httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`))
74
75 // Regexp match (could use httpmock.RegisterRegexpResponder instead)
76 httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/\d+\z`,
77 httpmock.NewStringResponder(200, `{"id": 1, "name": "My Great Article"}`))
78
79 // do stuff that makes a request to articles
80 ...
81
82 // get count info
83 httpmock.GetTotalCallCount()
84
85 // get the amount of calls for the registered responder
86 info := httpmock.GetCallCountInfo()
87 info["GET https://api.mybiz.com/articles"] // number of GET calls made to https://api.mybiz.com/articles
88 info["GET https://api.mybiz.com/articles/id/12"] // number of GET calls made to https://api.mybiz.com/articles/id/12
89 info[`GET =~^https://api\.mybiz\.com/articles/id/\d+\z`] // number of GET calls made to https://api.mybiz.com/articles/id/<any-number>
90}
91```
92
93### Advanced Example:
94```go
95func TestFetchArticles(t *testing.T) {
96 httpmock.Activate()
97 defer httpmock.DeactivateAndReset()
98
99 // our database of articles
100 articles := make([]map[string]interface{}, 0)
101
102 // mock to list out the articles
103 httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles",
104 func(req *http.Request) (*http.Response, error) {
105 resp, err := httpmock.NewJsonResponse(200, articles)
106 if err != nil {
107 return httpmock.NewStringResponse(500, ""), nil
108 }
109 return resp, nil
110 },
111 )
112
113 // return an article related to the request with the help of regexp submatch (\d+)
114 httpmock.RegisterResponder("GET", `=~^https://api\.mybiz\.com/articles/id/(\d+)\z`,
115 func(req *http.Request) (*http.Response, error) {
116 // Get ID from request
117 id := httpmock.MustGetSubmatchAsUint(req, 1) // 1=first regexp submatch
118 return httpmock.NewJsonResponse(200, map[string]interface{}{
119 "id": id,
120 "name": "My Great Article",
121 })
122 },
123 )
124
125 // mock to add a new article
126 httpmock.RegisterResponder("POST", "https://api.mybiz.com/articles",
127 func(req *http.Request) (*http.Response, error) {
128 article := make(map[string]interface{})
129 if err := json.NewDecoder(req.Body).Decode(&article); err != nil {
130 return httpmock.NewStringResponse(400, ""), nil
131 }
132
133 articles = append(articles, article)
134
135 resp, err := httpmock.NewJsonResponse(200, article)
136 if err != nil {
137 return httpmock.NewStringResponse(500, ""), nil
138 }
139 return resp, nil
140 },
141 )
142
143 // do stuff that adds and checks articles
144}
145```
146
147### Algorithm
148
149When `GET http://example.tld/some/path?b=12&a=foo&a=bar` request is
150caught, all standard responders are checked against the following URL
151or paths, the first match stops the search:
152
1531. `http://example.tld/some/path?b=12&a=foo&a=bar` (original URL)
1541. `http://example.tld/some/path?a=bar&a=foo&b=12` (sorted query params)
1551. `http://example.tld/some/path` (without query params)
1561. `/some/path?b=12&a=foo&a=bar` (original URL without scheme and host)
1571. `/some/path?a=bar&a=foo&b=12` (same, but sorted query params)
1581. `/some/path` (path only)
159
160If no standard responder matched, the regexp responders are checked,
161in the same order, the first match stops the search.
162
163
164### [go-testdeep](https://go-testdeep.zetta.rocks/) + [tdsuite](https://pkg.go.dev/github.com/maxatome/go-testdeep/helpers/tdsuite) example:
165```go
166// article_test.go
167
168import (
169 "testing"
170
171 "github.com/jarcoal/httpmock"
172 "github.com/maxatome/go-testdeep/helpers/tdsuite"
173 "github.com/maxatome/go-testdeep/td"
174)
175
176type MySuite struct{}
177
178func (s *MySuite) Setup(t *td.T) error {
179 // block all HTTP requests
180 httpmock.Activate()
181 return nil
182}
183
184func (s *MySuite) PostTest(t *td.T, testName string) error {
185 // remove any mocks after each test
186 httpmock.Reset()
187 return nil
188}
189
190func (s *MySuite) Destroy(t *td.T) error {
191 httpmock.DeactivateAndReset()
192 return nil
193}
194
195func TestMySuite(t *testing.T) {
196 tdsuite.Run(t, &MySuite{})
197}
198
199func (s *MySuite) TestArticles(assert, require *td.T) {
200 httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles.json",
201 httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`))
202
203 // do stuff that makes a request to articles.json
204}
205```
206
207
208### [Ginkgo](https://onsi.github.io/ginkgo/) example:
209```go
210// article_suite_test.go
211
212import (
213 // ...
214 "github.com/jarcoal/httpmock"
215)
216// ...
217var _ = BeforeSuite(func() {
218 // block all HTTP requests
219 httpmock.Activate()
220})
221
222var _ = BeforeEach(func() {
223 // remove any mocks
224 httpmock.Reset()
225})
226
227var _ = AfterSuite(func() {
228 httpmock.DeactivateAndReset()
229})
230
231
232// article_test.go
233
234import (
235 // ...
236 "github.com/jarcoal/httpmock"
237)
238
239var _ = Describe("Articles", func() {
240 It("returns a list of articles", func() {
241 httpmock.RegisterResponder("GET", "https://api.mybiz.com/articles.json",
242 httpmock.NewStringResponder(200, `[{"id": 1, "name": "My Great Article"}]`))
243
244 // do stuff that makes a request to articles.json
245 })
246})
247```
248
249### [Ginkgo](https://onsi.github.io/ginkgo/) + [Resty](https://github.com/go-resty/resty) Example:
250```go
251// article_suite_test.go
252
253import (
254 // ...
255 "github.com/jarcoal/httpmock"
256 "github.com/go-resty/resty"
257)
258// ...
259var _ = BeforeSuite(func() {
260 // block all HTTP requests
261 httpmock.ActivateNonDefault(resty.DefaultClient.GetClient())
262})
263
264var _ = BeforeEach(func() {
265 // remove any mocks
266 httpmock.Reset()
267})
268
269var _ = AfterSuite(func() {
270 httpmock.DeactivateAndReset()
271})
272
273
274// article_test.go
275
276import (
277 // ...
278 "github.com/jarcoal/httpmock"
279 "github.com/go-resty/resty"
280)
281
282var _ = Describe("Articles", func() {
283 It("returns a list of articles", func() {
284 fixture := `{"status":{"message": "Your message", "code": 200}}`
285 responder := httpmock.NewStringResponder(200, fixture)
286 fakeUrl := "https://api.mybiz.com/articles.json"
287 httpmock.RegisterResponder("GET", fakeUrl, responder)
288
289 // fetch the article into struct
290 articleObject := &models.Article{}
291 _, err := resty.R().SetResult(articleObject).Get(fakeUrl)
292
293 // do stuff with the article object ...
294 })
295})
296```
View as plain text