Skip to content

Commit 6150913

Browse files
authored
Merge pull request #104 from c-roussel/add-multiple-responses-responder
feat: multiple responses responder
2 parents c34dbbb + d1b2862 commit 6150913

File tree

2 files changed

+136
-0
lines changed

2 files changed

+136
-0
lines changed

response.go

+66
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"net/http"
1010
"strconv"
1111
"strings"
12+
"sync"
1213

1314
"github.com/jarcoal/httpmock/internal"
1415
)
@@ -125,6 +126,71 @@ func ResponderFromResponse(resp *http.Response) Responder {
125126
}
126127
}
127128

129+
// ResponderFromMultipleResponses wraps an *http.Response list in a Responder.
130+
//
131+
// Each response will be returned in the order of the provided list.
132+
// If the responder is called more than the size of the provided list, an error
133+
// will be thrown.
134+
//
135+
// Be careful, except for responses generated by httpmock
136+
// (NewStringResponse and NewBytesResponse functions) for which there
137+
// is no problems, it is the caller responsibility to ensure the
138+
// response body can be read several times and concurrently if needed,
139+
// as it is shared among all Responder returned responses.
140+
//
141+
// For home-made responses, NewRespBodyFromString and
142+
// NewRespBodyFromBytes functions can be used to produce response
143+
// bodies that can be read several times and concurrently.
144+
//
145+
// If all responses have been returned and fn is passed
146+
// and non-nil, it acts as the fn parameter of NewNotFoundResponder,
147+
// allowing to dump the stack trace to localize the origin of the
148+
// call.
149+
// import (
150+
// "github.com/jarcoal/httpmock"
151+
// "testing"
152+
// )
153+
// ...
154+
// func TestMyApp(t *testing.T) {
155+
// ...
156+
// // This responder is callable only once, then an error is returned and
157+
// // the stacktrace of the call logged using t.Log()
158+
// httpmock.RegisterResponder("GET", "/foo/bar",
159+
// httpmock.ResponderFromMultipleResponses(
160+
// []*http.Response{
161+
// httpmock.NewStringResponse(200, `{"name":"bar"}`),
162+
// httpmock.NewStringResponse(404, `{"mesg":"Not found"}`),
163+
// },
164+
// t.Log),
165+
// )
166+
// }
167+
func ResponderFromMultipleResponses(responses []*http.Response, fn ...func(...interface{})) Responder {
168+
responseIndex := 0
169+
mutex := sync.Mutex{}
170+
return func(req *http.Request) (*http.Response, error) {
171+
mutex.Lock()
172+
defer mutex.Unlock()
173+
defer func() { responseIndex++ }()
174+
if responseIndex >= len(responses) {
175+
err := internal.StackTracer{
176+
Err: fmt.Errorf("not enough responses provided: responder called %d time(s) but %d response(s) provided", responseIndex+1, len(responses)),
177+
}
178+
if len(fn) > 0 {
179+
err.CustomFn = fn[0]
180+
}
181+
return nil, err
182+
}
183+
res := *responses[responseIndex]
184+
// Our stuff: generate a new io.ReadCloser instance sharing the same buffer
185+
if body, ok := responses[responseIndex].Body.(*dummyReadCloser); ok {
186+
res.Body = body.copy()
187+
}
188+
189+
res.Request = req
190+
return &res, nil
191+
}
192+
}
193+
128194
// NewErrorResponder creates a Responder that returns an empty request and the
129195
// given error. This can be used to e.g. imitate more deep http errors for the
130196
// client.

response_test.go

+70
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,76 @@ func TestResponderFromResponse(t *testing.T) {
5353
}
5454
}
5555

56+
func TestResponderFromResponses(t *testing.T) {
57+
jsonResponse, err := NewJsonResponse(200, map[string]string{"test": "toto"})
58+
if err != nil {
59+
t.Errorf("NewJsonResponse failed: %s", err)
60+
}
61+
62+
responder := ResponderFromMultipleResponses(
63+
[]*http.Response{
64+
jsonResponse,
65+
NewStringResponse(200, "hello world"),
66+
},
67+
)
68+
69+
req, err := http.NewRequest(http.MethodGet, testURL, nil)
70+
if err != nil {
71+
t.Fatal("Error creating request")
72+
}
73+
response1, err := responder(req)
74+
if err != nil {
75+
t.Error("Error should be nil")
76+
}
77+
78+
testURLWithQuery := testURL + "?a=1"
79+
req, err = http.NewRequest(http.MethodGet, testURLWithQuery, nil)
80+
if err != nil {
81+
t.Fatal("Error creating request")
82+
}
83+
response2, err := responder(req)
84+
if err != nil {
85+
t.Error("Error should be nil")
86+
}
87+
88+
// Body should be the same for both responses
89+
assertBody(t, response1, `{"test":"toto"}`)
90+
assertBody(t, response2, "hello world")
91+
92+
// Request should be non-nil and different for each response
93+
if response1.Request != nil && response2.Request != nil {
94+
if response1.Request.URL.String() != testURL {
95+
t.Errorf("Expected request url %s, got: %s", testURL, response1.Request.URL.String())
96+
}
97+
if response2.Request.URL.String() != testURLWithQuery {
98+
t.Errorf("Expected request url %s, got: %s", testURLWithQuery, response2.Request.URL.String())
99+
}
100+
} else {
101+
t.Error("response.Request should not be nil")
102+
}
103+
104+
// ensure we can't call the responder more than the number of responses it embeds
105+
_, err = responder(req)
106+
if err == nil {
107+
t.Error("Error should not be nil")
108+
} else if err.Error() != "not enough responses provided: responder called 3 time(s) but 2 response(s) provided" {
109+
t.Error("Invalid error message")
110+
}
111+
112+
// fn usage
113+
responder = ResponderFromMultipleResponses([]*http.Response{}, func(args ...interface{}) {})
114+
_, err = responder(req)
115+
if err == nil {
116+
t.Error("Error should not be nil")
117+
} else if err.Error() != "not enough responses provided: responder called 1 time(s) but 0 response(s) provided" {
118+
t.Errorf("Invalid error message")
119+
} else if ne, ok := err.(internal.StackTracer); !ok {
120+
t.Errorf(`err type mismatch, got %T, expected internal.StackTracer`, err)
121+
} else if ne.CustomFn == nil {
122+
t.Error(`ne.CustomFn should not be nil`)
123+
}
124+
}
125+
56126
func TestNewNotFoundResponder(t *testing.T) {
57127
responder := NewNotFoundResponder(func(args ...interface{}) {})
58128

0 commit comments

Comments
 (0)