1package httpmock 2 3import ( 4 "bytes" 5 "encoding/json" 6 "encoding/xml" 7 "fmt" 8 "io" 9 "net/http" 10 "strconv" 11 "strings" 12) 13 14// Responder is a callback that receives and http request and returns 15// a mocked response. 16type Responder func(*http.Request) (*http.Response, error) 17 18func (r Responder) times(name string, n int, fn ...func(...interface{})) Responder { 19 count := 0 20 return func(req *http.Request) (*http.Response, error) { 21 count++ 22 if count > n { 23 err := stackTracer{ 24 err: fmt.Errorf("Responder not found for %s %s (coz %s and already called %d times)", req.Method, req.URL, name, count), 25 } 26 if len(fn) > 0 { 27 err.customFn = fn[0] 28 } 29 return nil, err 30 } 31 return r(req) 32 } 33} 34 35// Times returns a Responder callable n times before returning an 36// error. If the Responder is called more than n times and fn is 37// passed and non-nil, it acts as the fn parameter of 38// NewNotFoundResponder, allowing to dump the stack trace to localize 39// the origin of the call. 40func (r Responder) Times(n int, fn ...func(...interface{})) Responder { 41 return r.times("Times", n, fn...) 42} 43 44// Once returns a new Responder callable once before returning an 45// error. If the Responder is called 2 or more times and fn is passed 46// and non-nil, it acts as the fn parameter of NewNotFoundResponder, 47// allowing to dump the stack trace to localize the origin of the 48// call. 49func (r Responder) Once(fn ...func(...interface{})) Responder { 50 return r.times("Once", 1, fn...) 51} 52 53// Trace returns a new Responder that allow to easily trace the calls 54// of the original Responder using fn. It can be used in conjunction 55// with the testing package as in the example below with the help of 56// (*testing.T).Log method: 57// import "testing" 58// ... 59// func TestMyApp(t *testing.T) { 60// ... 61// httpmock.RegisterResponder("GET", "/foo/bar", 62// httpmock.NewStringResponder(200, "{}").Trace(t.Log), 63// ) 64func (r Responder) Trace(fn func(...interface{})) Responder { 65 return func(req *http.Request) (*http.Response, error) { 66 resp, err := r(req) 67 return resp, stackTracer{ 68 customFn: fn, 69 err: err, 70 } 71 } 72} 73 74// ResponderFromResponse wraps an *http.Response in a Responder 75func ResponderFromResponse(resp *http.Response) Responder { 76 return func(req *http.Request) (*http.Response, error) { 77 res := new(http.Response) 78 *res = *resp 79 res.Request = req 80 return res, nil 81 } 82} 83 84// NewErrorResponder creates a Responder that returns an empty request and the 85// given error. This can be used to e.g. imitate more deep http errors for the 86// client. 87func NewErrorResponder(err error) Responder { 88 return func(req *http.Request) (*http.Response, error) { 89 return nil, err 90 } 91} 92 93// NewNotFoundResponder creates a Responder typically used in 94// conjunction with RegisterNoResponder() function and testing 95// package, to be proactive when a Responder is not found. fn is 96// called with a unique string parameter containing the name of the 97// missing route and the stack trace to localize the origin of the 98// call. If fn returns (= if it does not panic), the responder returns 99// an error of the form: "Responder not found for GET http://foo.bar/path". 100// Note that fn can be nil. 101// 102// It is useful when writing tests to ensure that all routes have been 103// mocked. 104// 105// Example of use: 106// import "testing" 107// ... 108// func TestMyApp(t *testing.T) { 109// ... 110// // Calls testing.Fatal with the name of Responder-less route and 111// // the stack trace of the call. 112// httpmock.RegisterNoResponder(httpmock.NewNotFoundResponder(t.Fatal)) 113// 114// Will abort the current test and print something like: 115// transport_test.go:735: Called from net/http.Get() 116// at /go/src/github.com/jarcoal/httpmock/transport_test.go:714 117// github.com/jarcoal/httpmock.TestCheckStackTracer() 118// at /go/src/testing/testing.go:865 119// testing.tRunner() 120// at /go/src/runtime/asm_amd64.s:1337 121func NewNotFoundResponder(fn func(...interface{})) Responder { 122 return func(req *http.Request) (*http.Response, error) { 123 return nil, stackTracer{ 124 customFn: fn, 125 err: fmt.Errorf("Responder not found for %s %s", req.Method, req.URL), 126 } 127 } 128} 129 130// NewStringResponse creates an *http.Response with a body based on the given string. Also accepts 131// an http status code. 132func NewStringResponse(status int, body string) *http.Response { 133 return &http.Response{ 134 Status: strconv.Itoa(status), 135 StatusCode: status, 136 Body: NewRespBodyFromString(body), 137 Header: http.Header{}, 138 ContentLength: -1, 139 } 140} 141 142// NewStringResponder creates a Responder from a given body (as a string) and status code. 143func NewStringResponder(status int, body string) Responder { 144 return ResponderFromResponse(NewStringResponse(status, body)) 145} 146 147// NewBytesResponse creates an *http.Response with a body based on the given bytes. Also accepts 148// an http status code. 149func NewBytesResponse(status int, body []byte) *http.Response { 150 return &http.Response{ 151 Status: strconv.Itoa(status), 152 StatusCode: status, 153 Body: NewRespBodyFromBytes(body), 154 Header: http.Header{}, 155 ContentLength: -1, 156 } 157} 158 159// NewBytesResponder creates a Responder from a given body (as a byte slice) and status code. 160func NewBytesResponder(status int, body []byte) Responder { 161 return ResponderFromResponse(NewBytesResponse(status, body)) 162} 163 164// NewJsonResponse creates an *http.Response with a body that is a json encoded representation of 165// the given interface{}. Also accepts an http status code. 166func NewJsonResponse(status int, body interface{}) (*http.Response, error) { // nolint: golint 167 encoded, err := json.Marshal(body) 168 if err != nil { 169 return nil, err 170 } 171 response := NewBytesResponse(status, encoded) 172 response.Header.Set("Content-Type", "application/json") 173 return response, nil 174} 175 176// NewJsonResponder creates a Responder from a given body (as an interface{} that is encoded to 177// json) and status code. 178func NewJsonResponder(status int, body interface{}) (Responder, error) { // nolint: golint 179 resp, err := NewJsonResponse(status, body) 180 if err != nil { 181 return nil, err 182 } 183 return ResponderFromResponse(resp), nil 184} 185 186// NewJsonResponderOrPanic is like NewJsonResponder but panics in case of error. 187// 188// It simplifies the call of RegisterResponder, avoiding the use of a 189// temporary variable and an error check, and so can be used as 190// NewStringResponder or NewBytesResponder in such context: 191// RegisterResponder( 192// "GET", 193// "/test/path", 194// NewJSONResponderOrPanic(200, &MyBody), 195// ) 196func NewJsonResponderOrPanic(status int, body interface{}) Responder { // nolint: golint 197 responder, err := NewJsonResponder(status, body) 198 if err != nil { 199 panic(err) 200 } 201 return responder 202} 203 204// NewXmlResponse creates an *http.Response with a body that is an xml encoded representation 205// of the given interface{}. Also accepts an http status code. 206func NewXmlResponse(status int, body interface{}) (*http.Response, error) { // nolint: golint 207 encoded, err := xml.Marshal(body) 208 if err != nil { 209 return nil, err 210 } 211 response := NewBytesResponse(status, encoded) 212 response.Header.Set("Content-Type", "application/xml") 213 return response, nil 214} 215 216// NewXmlResponder creates a Responder from a given body (as an interface{} that is encoded to xml) 217// and status code. 218func NewXmlResponder(status int, body interface{}) (Responder, error) { // nolint: golint 219 resp, err := NewXmlResponse(status, body) 220 if err != nil { 221 return nil, err 222 } 223 return ResponderFromResponse(resp), nil 224} 225 226// NewXmlResponderOrPanic is like NewXmlResponder but panics in case of error. 227// 228// It simplifies the call of RegisterResponder, avoiding the use of a 229// temporary variable and an error check, and so can be used as 230// NewStringResponder or NewBytesResponder in such context: 231// RegisterResponder( 232// "GET", 233// "/test/path", 234// NewXmlResponderOrPanic(200, &MyBody), 235// ) 236func NewXmlResponderOrPanic(status int, body interface{}) Responder { // nolint: golint 237 responder, err := NewXmlResponder(status, body) 238 if err != nil { 239 panic(err) 240 } 241 return responder 242} 243 244// NewRespBodyFromString creates an io.ReadCloser from a string that is suitable for use as an 245// http response body. 246func NewRespBodyFromString(body string) io.ReadCloser { 247 return &dummyReadCloser{strings.NewReader(body)} 248} 249 250// NewRespBodyFromBytes creates an io.ReadCloser from a byte slice that is suitable for use as an 251// http response body. 252func NewRespBodyFromBytes(body []byte) io.ReadCloser { 253 return &dummyReadCloser{bytes.NewReader(body)} 254} 255 256type dummyReadCloser struct { 257 body io.ReadSeeker 258} 259 260func (d *dummyReadCloser) Read(p []byte) (n int, err error) { 261 n, err = d.body.Read(p) 262 if err == io.EOF { 263 d.body.Seek(0, 0) // nolint: errcheck 264 } 265 return n, err 266} 267 268func (d *dummyReadCloser) Close() error { 269 d.body.Seek(0, 0) // nolint: errcheck 270 return nil 271} 272