1 package httpmock_test
2
3 import (
4 "encoding/xml"
5 "errors"
6 "fmt"
7 "io"
8 "io/ioutil"
9 "net/http"
10 "path/filepath"
11 "strings"
12 "sync"
13 "testing"
14 "time"
15
16 "github.com/maxatome/go-testdeep/td"
17
18 . "github.com/jarcoal/httpmock"
19 "github.com/jarcoal/httpmock/internal"
20 )
21
22 func TestResponderFromResponse(t *testing.T) {
23 assert, require := td.AssertRequire(t)
24
25 responder := ResponderFromResponse(NewStringResponse(200, "hello world"))
26
27 req, err := http.NewRequest(http.MethodGet, testURL, nil)
28 require.CmpNoError(err)
29
30 response1, err := responder(req)
31 require.CmpNoError(err)
32
33 testURLWithQuery := testURL + "?a=1"
34 req, err = http.NewRequest(http.MethodGet, testURLWithQuery, nil)
35 require.CmpNoError(err)
36
37 response2, err := responder(req)
38 require.CmpNoError(err)
39
40
41 assertBody(assert, response1, "hello world")
42 assertBody(assert, response2, "hello world")
43
44
45 require.NotNil(response1.Request)
46 assert.String(response1.Request.URL, testURL)
47
48 require.NotNil(response2.Request)
49 assert.String(response2.Request.URL, testURLWithQuery)
50 }
51
52 func TestResponderFromResponses(t *testing.T) {
53 assert, require := td.AssertRequire(t)
54
55 jsonResponse, err := NewJsonResponse(200, map[string]string{"test": "toto"})
56 require.CmpNoError(err)
57
58 responder := ResponderFromMultipleResponses(
59 []*http.Response{
60 jsonResponse,
61 NewStringResponse(200, "hello world"),
62 },
63 )
64
65 req, err := http.NewRequest(http.MethodGet, testURL, nil)
66 require.CmpNoError(err)
67
68 response1, err := responder(req)
69 require.CmpNoError(err)
70
71 testURLWithQuery := testURL + "?a=1"
72 req, err = http.NewRequest(http.MethodGet, testURLWithQuery, nil)
73 require.CmpNoError(err)
74
75 response2, err := responder(req)
76 require.CmpNoError(err)
77
78
79 assertBody(assert, response1, `{"test":"toto"}`)
80 assertBody(assert, response2, "hello world")
81
82
83 require.NotNil(response1.Request)
84 assert.String(response1.Request.URL, testURL)
85
86 require.NotNil(response2.Request)
87 assert.String(response2.Request.URL, testURLWithQuery)
88
89
90 _, err = responder(req)
91 assert.String(err, "not enough responses provided: responder called 3 time(s) but 2 response(s) provided")
92
93
94 responder = ResponderFromMultipleResponses([]*http.Response{}, func(args ...interface{}) {})
95 _, err = responder(req)
96 assert.String(err, "not enough responses provided: responder called 1 time(s) but 0 response(s) provided")
97 if assert.Isa(err, internal.StackTracer{}) {
98 assert.NotNil(err.(internal.StackTracer).CustomFn)
99 }
100 }
101
102 func TestNewNotFoundResponder(t *testing.T) {
103 assert, require := td.AssertRequire(t)
104
105 responder := NewNotFoundResponder(func(args ...interface{}) {})
106
107 req, err := http.NewRequest("GET", "http://foo.bar/path", nil)
108 require.CmpNoError(err)
109
110 const title = "Responder not found for GET http://foo.bar/path"
111
112 resp, err := responder(req)
113 assert.Nil(resp)
114 assert.String(err, title)
115 if assert.Isa(err, internal.StackTracer{}) {
116 assert.NotNil(err.(internal.StackTracer).CustomFn)
117 }
118
119
120 responder = NewNotFoundResponder(nil)
121
122 resp, err = responder(req)
123 assert.Nil(resp)
124 assert.String(err, title)
125 if assert.Isa(err, internal.StackTracer{}) {
126 assert.Nil(err.(internal.StackTracer).CustomFn)
127 }
128 }
129
130 func TestNewStringResponse(t *testing.T) {
131 assert, require := td.AssertRequire(t)
132
133 const (
134 body = "hello world"
135 status = 200
136 )
137 response := NewStringResponse(status, body)
138
139 data, err := ioutil.ReadAll(response.Body)
140 require.CmpNoError(err)
141
142 assert.String(data, body)
143 assert.Cmp(response.StatusCode, status)
144 }
145
146 func TestNewBytesResponse(t *testing.T) {
147 assert, require := td.AssertRequire(t)
148
149 const (
150 body = "hello world"
151 status = 200
152 )
153 response := NewBytesResponse(status, []byte(body))
154
155 data, err := ioutil.ReadAll(response.Body)
156 require.CmpNoError(err)
157
158 assert.String(data, body)
159 assert.Cmp(response.StatusCode, status)
160 }
161
162 func TestNewJsonResponse(t *testing.T) {
163 assert := td.Assert(t)
164
165 type schema struct {
166 Hello string `json:"hello"`
167 }
168
169 dir, cleanup := tmpDir(assert)
170 defer cleanup()
171 fileName := filepath.Join(dir, "ok.json")
172 writeFile(assert, fileName, []byte(`{ "test": true }`))
173
174 for i, test := range []struct {
175 body interface{}
176 expected string
177 }{
178 {body: &schema{"world"}, expected: `{"hello":"world"}`},
179 {body: File(fileName), expected: `{"test":true}`},
180 } {
181 assert.Run(fmt.Sprintf("#%d", i), func(assert *td.T) {
182 response, err := NewJsonResponse(200, test.body)
183 if !assert.CmpNoError(err) {
184 return
185 }
186 assert.Cmp(response.StatusCode, 200)
187 assert.Cmp(response.Header.Get("Content-Type"), "application/json")
188 assertBody(assert, response, test.expected)
189 })
190 }
191
192
193 response, err := NewJsonResponse(200, func() {})
194 assert.CmpError(err)
195 assert.Nil(response)
196 }
197
198 func checkResponder(assert *td.T, r Responder, expectedStatus int, expectedBody string) {
199 assert.Helper()
200
201 req, err := http.NewRequest(http.MethodGet, "/foo", nil)
202 assert.FailureIsFatal().CmpNoError(err)
203
204 resp, err := r(req)
205 if !assert.CmpNoError(err, "Responder returned no error") {
206 return
207 }
208
209 if !assert.NotNil(resp, "Responder returned a non-nil response") {
210 return
211 }
212
213 assert.Cmp(resp.StatusCode, expectedStatus, "Status code is OK")
214 assertBody(assert, resp, expectedBody)
215 }
216
217 func TestNewJsonResponder(t *testing.T) {
218 assert := td.Assert(t)
219
220 assert.Run("OK", func(assert *td.T) {
221 r, err := NewJsonResponder(200, map[string]int{"foo": 42})
222 if assert.CmpNoError(err) {
223 checkResponder(assert, r, 200, `{"foo":42}`)
224 }
225 })
226
227 assert.Run("OK file", func(assert *td.T) {
228 dir, cleanup := tmpDir(assert)
229 defer cleanup()
230 fileName := filepath.Join(dir, "ok.json")
231 writeFile(assert, fileName, []byte(`{ "foo" : 42 }`))
232
233 r, err := NewJsonResponder(200, File(fileName))
234 if assert.CmpNoError(err) {
235 checkResponder(assert, r, 200, `{"foo":42}`)
236 }
237 })
238
239 assert.Run("Error", func(assert *td.T) {
240 r, err := NewJsonResponder(200, func() {})
241 assert.CmpError(err)
242 assert.Nil(r)
243 })
244
245 assert.Run("OK don't panic", func(assert *td.T) {
246 assert.CmpNotPanic(
247 func() {
248 r := NewJsonResponderOrPanic(200, map[string]int{"foo": 42})
249 checkResponder(assert, r, 200, `{"foo":42}`)
250 })
251 })
252
253 assert.Run("Panic", func(assert *td.T) {
254 assert.CmpPanic(
255 func() { NewJsonResponderOrPanic(200, func() {}) },
256 td.Ignore())
257 })
258 }
259
260 type schemaXML struct {
261 Hello string `xml:"hello"`
262 }
263
264 func TestNewXmlResponse(t *testing.T) {
265 assert := td.Assert(t)
266
267 body := &schemaXML{"world"}
268
269 b, err := xml.Marshal(body)
270 if err != nil {
271 t.Fatalf("Cannot xml.Marshal expected body: %s", err)
272 }
273 expectedBody := string(b)
274
275 dir, cleanup := tmpDir(assert)
276 defer cleanup()
277 fileName := filepath.Join(dir, "ok.xml")
278 writeFile(assert, fileName, b)
279
280 for i, test := range []struct {
281 body interface{}
282 expected string
283 }{
284 {body: body, expected: expectedBody},
285 {body: File(fileName), expected: expectedBody},
286 } {
287 assert.Run(fmt.Sprintf("#%d", i), func(assert *td.T) {
288 response, err := NewXmlResponse(200, test.body)
289 if !assert.CmpNoError(err) {
290 return
291 }
292 assert.Cmp(response.StatusCode, 200)
293 assert.Cmp(response.Header.Get("Content-Type"), "application/xml")
294 assertBody(assert, response, test.expected)
295 })
296 }
297
298
299 response, err := NewXmlResponse(200, func() {})
300 assert.CmpError(err)
301 assert.Nil(response)
302 }
303
304 func TestNewXmlResponder(t *testing.T) {
305 assert, require := td.AssertRequire(t)
306
307 body := &schemaXML{"world"}
308
309 b, err := xml.Marshal(body)
310 require.CmpNoError(err)
311 expectedBody := string(b)
312
313 assert.Run("OK", func(assert *td.T) {
314 r, err := NewXmlResponder(200, body)
315 if assert.CmpNoError(err) {
316 checkResponder(assert, r, 200, expectedBody)
317 }
318 })
319
320 assert.Run("OK file", func(assert *td.T) {
321 dir, cleanup := tmpDir(assert)
322 defer cleanup()
323 fileName := filepath.Join(dir, "ok.xml")
324 writeFile(assert, fileName, b)
325
326 r, err := NewXmlResponder(200, File(fileName))
327 if assert.CmpNoError(err) {
328 checkResponder(assert, r, 200, expectedBody)
329 }
330 })
331
332 assert.Run("Error", func(assert *td.T) {
333 r, err := NewXmlResponder(200, func() {})
334 assert.CmpError(err)
335 assert.Nil(r)
336 })
337
338 assert.Run("OK don't panic", func(assert *td.T) {
339 assert.CmpNotPanic(
340 func() {
341 r := NewXmlResponderOrPanic(200, body)
342 checkResponder(assert, r, 200, expectedBody)
343 })
344 })
345
346 assert.Run("Panic", func(assert *td.T) {
347 assert.CmpPanic(
348 func() { NewXmlResponderOrPanic(200, func() {}) },
349 td.Ignore())
350 })
351 }
352
353 func TestNewErrorResponder(t *testing.T) {
354 assert, require := td.AssertRequire(t)
355
356 origError := errors.New("oh no")
357 responder := NewErrorResponder(origError)
358
359 req, err := http.NewRequest(http.MethodGet, testURL, nil)
360 require.CmpNoError(err)
361
362 response, err := responder(req)
363 assert.Cmp(err, origError)
364 assert.Nil(response)
365 }
366
367 func TestResponseBody(t *testing.T) {
368 assert := td.Assert(t)
369
370 const (
371 body = "hello world"
372 status = 200
373 )
374
375 assert.Run("http.Response", func(assert *td.T) {
376 for i, response := range []*http.Response{
377 NewBytesResponse(status, []byte(body)),
378 NewStringResponse(status, body),
379 } {
380 assert.Run(fmt.Sprintf("resp #%d", i), func(assert *td.T) {
381 assertBody(assert, response, body)
382
383 assert.Cmp(response.StatusCode, status)
384
385 var buf [1]byte
386 _, err := response.Body.Read(buf[:])
387 assert.Cmp(err, io.EOF)
388 })
389 }
390 })
391
392 assert.Run("Responder", func(assert *td.T) {
393 for i, responder := range []Responder{
394 NewBytesResponder(200, []byte(body)),
395 NewStringResponder(200, body),
396 } {
397 assert.Run(fmt.Sprintf("resp #%d", i), func(assert *td.T) {
398 req, _ := http.NewRequest("GET", "http://foo.bar", nil)
399 response, err := responder(req)
400 if !assert.CmpNoError(err) {
401 return
402 }
403
404 assertBody(assert, response, body)
405
406 var buf [1]byte
407 _, err = response.Body.Read(buf[:])
408 assert.Cmp(err, io.EOF)
409 })
410 }
411 })
412 }
413
414 func TestResponder(t *testing.T) {
415 req, err := http.NewRequest(http.MethodGet, "http://foo.bar", nil)
416 td.Require(t).CmpNoError(err)
417
418 resp := &http.Response{}
419
420 chk := func(r Responder, expectedResp *http.Response, expectedErr string) {
421 t.Helper()
422 gotResp, gotErr := r(req)
423 td.CmpShallow(t, gotResp, expectedResp)
424 var gotErrStr string
425 if gotErr != nil {
426 gotErrStr = gotErr.Error()
427 }
428 td.Cmp(t, gotErrStr, expectedErr)
429 }
430 called := false
431 chkNotCalled := func() {
432 if called {
433 t.Helper()
434 t.Errorf("Original responder should not be called")
435 called = false
436 }
437 }
438 chkCalled := func() {
439 if !called {
440 t.Helper()
441 t.Errorf("Original responder should be called")
442 }
443 called = false
444 }
445
446 r := Responder(func(*http.Request) (*http.Response, error) {
447 called = true
448 return resp, nil
449 })
450 chk(r, resp, "")
451 chkCalled()
452
453
454
455 ro := r.Once()
456 chk(ro, resp, "")
457 chkCalled()
458
459 chk(ro, nil, "Responder not found for GET http://foo.bar (coz Once and already called 2 times)")
460 chkNotCalled()
461
462 chk(ro, nil, "Responder not found for GET http://foo.bar (coz Once and already called 3 times)")
463 chkNotCalled()
464
465 ro = r.Once(func(args ...interface{}) {})
466 chk(ro, resp, "")
467 chkCalled()
468
469 chk(ro, nil, "Responder not found for GET http://foo.bar (coz Once and already called 2 times)")
470 chkNotCalled()
471
472
473
474 rt := r.Times(2)
475 chk(rt, resp, "")
476 chkCalled()
477
478 chk(rt, resp, "")
479 chkCalled()
480
481 chk(rt, nil, "Responder not found for GET http://foo.bar (coz Times and already called 3 times)")
482 chkNotCalled()
483
484 chk(rt, nil, "Responder not found for GET http://foo.bar (coz Times and already called 4 times)")
485 chkNotCalled()
486
487 rt = r.Times(1, func(args ...interface{}) {})
488 chk(rt, resp, "")
489 chkCalled()
490
491 chk(rt, nil, "Responder not found for GET http://foo.bar (coz Times and already called 2 times)")
492 chkNotCalled()
493
494
495
496 rt = r.Trace(func(args ...interface{}) {})
497 chk(rt, resp, "")
498 chkCalled()
499
500 chk(rt, resp, "")
501 chkCalled()
502
503
504
505 rt = r.Delay(100 * time.Millisecond)
506 before := time.Now()
507 chk(rt, resp, "")
508 duration := time.Since(before)
509 chkCalled()
510 td.Cmp(t, duration, td.Gte(100*time.Millisecond), "Responder is delayed")
511 }
512
513 func TestResponder_Then(t *testing.T) {
514 assert, require := td.AssertRequire(t)
515
516 req, err := http.NewRequest(http.MethodGet, "http://foo.bar", nil)
517 require.CmpNoError(err)
518
519
520
521 var stack string
522 newResponder := func(level string) Responder {
523 return func(*http.Request) (*http.Response, error) {
524 stack += level
525 return NewStringResponse(200, level), nil
526 }
527 }
528 var rt Responder
529 chk := func(assert *td.T, expectedLevel, expectedStack string) {
530 assert.Helper()
531 resp, err := rt(req)
532 if !assert.CmpNoError(err, "Responder call") {
533 return
534 }
535 b, err := ioutil.ReadAll(resp.Body)
536 if !assert.CmpNoError(err, "Read response") {
537 return
538 }
539 assert.String(b, expectedLevel)
540 assert.Cmp(stack, expectedStack)
541 }
542
543 A, B, C := newResponder("A"), newResponder("B"), newResponder("C")
544 D, E, F := newResponder("D"), newResponder("E"), newResponder("F")
545
546 assert.Run("simple", func(assert *td.T) {
547
548 rt = A.Then(B)
549
550 chk(assert, "A", "A")
551 chk(assert, "B", "AB")
552 chk(assert, "B", "ABB")
553 chk(assert, "B", "ABBB")
554 })
555
556 stack = ""
557
558 assert.Run("simple chained", func(assert *td.T) {
559
560
561
562
563
564 rt = A.Then(B).
565 Then(C).
566 Then(D).
567 Then(E).
568 Then(F)
569
570 chk(assert, "A", "A")
571 chk(assert, "B", "AB")
572 chk(assert, "C", "ABC")
573 chk(assert, "D", "ABCD")
574 chk(assert, "E", "ABCDE")
575 chk(assert, "F", "ABCDEF")
576 chk(assert, "F", "ABCDEFF")
577 chk(assert, "F", "ABCDEFFF")
578 })
579
580 stack = ""
581
582 assert.Run("Then Responder as Then param", func(assert *td.T) {
583 assert.CmpPanic(
584 func() { A.Then(B.Then(C)) },
585 "Then() does not accept another Then() Responder as parameter")
586 })
587 }
588
589 func TestParallelResponder(t *testing.T) {
590 req, err := http.NewRequest(http.MethodGet, "http://foo.bar", nil)
591 td.Require(t).CmpNoError(err)
592
593 body := strings.Repeat("ABC-", 1000)
594
595 for ir, r := range []Responder{
596 NewStringResponder(200, body),
597 NewBytesResponder(200, []byte(body)),
598 } {
599 var wg sync.WaitGroup
600 for n := 0; n < 100; n++ {
601 wg.Add(1)
602 go func() {
603 defer wg.Done()
604 resp, _ := r(req)
605 b, err := ioutil.ReadAll(resp.Body)
606 td.CmpNoError(t, err, "resp #%d", ir)
607 td.CmpLen(t, b, 4000, "resp #%d", ir)
608 td.CmpHasPrefix(t, b, "ABC-", "resp #%d", ir)
609 }()
610 }
611 wg.Wait()
612 }
613 }
614
View as plain text