Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: multiple responses responder #104

Merged
merged 2 commits into from
Jan 27, 2021
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions response.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import (
"net/http"
"strconv"
"strings"
"sync"

"github.com/jarcoal/httpmock/internal"
)
Expand Down Expand Up @@ -125,6 +126,48 @@ func ResponderFromResponse(resp *http.Response) Responder {
}
}

// ResponderFromMultipleResponses wraps an *http.Response list in a Responder.
//
// Each response will be returned in the order of the provided list.
// If the responder is called more than the size of the provided list, an error
// will be thrown.
//
// Be careful, except for responses generated by httpmock
// (NewStringResponse and NewBytesResponse functions) for which there
// is no problems, it is the caller responsibility to ensure the
// response body can be read several times and concurrently if needed,
// as it is shared among all Responder returned responses.
//
// For home-made responses, NewRespBodyFromString and
// NewRespBodyFromBytes functions can be used to produce response
// bodies that can be read several times and concurrently.
func ResponderFromMultipleResponses(responses []*http.Response, fn ...func(...interface{})) Responder {
responseIndex := 0
mutex := sync.Mutex{}
return func(req *http.Request) (*http.Response, error) {
mutex.Lock()
defer mutex.Unlock()
defer func() { responseIndex++ }()
if responseIndex >= len(responses) {
err := internal.StackTracer{
Err: fmt.Errorf("not enough responses provided: responder called %d time(s) but %d response(s) provided", responseIndex+1, len(responses)),
}
if len(fn) > 0 {
err.CustomFn = fn[0]
}
return nil, err
}
res := *responses[responseIndex]
// Our stuff: generate a new io.ReadCloser instance sharing the same buffer
if body, ok := responses[responseIndex].Body.(*dummyReadCloser); ok {
res.Body = body.copy()
}

res.Request = req
return &res, nil
}
}

// NewErrorResponder creates a Responder that returns an empty request and the
// given error. This can be used to e.g. imitate more deep http errors for the
// client.
Expand Down
70 changes: 70 additions & 0 deletions response_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,76 @@ func TestResponderFromResponse(t *testing.T) {
}
}

func TestResponderFromResponses(t *testing.T) {
jsonResponse, err := NewJsonResponse(200, map[string]string{"test": "toto"})
if err != nil {
t.Errorf("NewJsonResponse failed: %s", err)
}

responder := ResponderFromMultipleResponses(
[]*http.Response{
jsonResponse,
NewStringResponse(200, "hello world"),
},
)

req, err := http.NewRequest(http.MethodGet, testURL, nil)
if err != nil {
t.Fatal("Error creating request")
}
response1, err := responder(req)
if err != nil {
t.Error("Error should be nil")
}

testURLWithQuery := testURL + "?a=1"
req, err = http.NewRequest(http.MethodGet, testURLWithQuery, nil)
if err != nil {
t.Fatal("Error creating request")
}
response2, err := responder(req)
if err != nil {
t.Error("Error should be nil")
}

// Body should be the same for both responses
assertBody(t, response1, `{"test":"toto"}`)
assertBody(t, response2, "hello world")

// Request should be non-nil and different for each response
if response1.Request != nil && response2.Request != nil {
if response1.Request.URL.String() != testURL {
t.Errorf("Expected request url %s, got: %s", testURL, response1.Request.URL.String())
}
if response2.Request.URL.String() != testURLWithQuery {
t.Errorf("Expected request url %s, got: %s", testURLWithQuery, response2.Request.URL.String())
}
} else {
t.Error("response.Request should not be nil")
}

// ensure we can't call the responder more than the number of responses it embeds
_, err = responder(req)
if err == nil {
t.Error("Error should not be nil")
} else if err.Error() != "not enough responses provided: responder called 3 time(s) but 2 response(s) provided" {
t.Error("Invalid error message")
}

// fn usage
responder = ResponderFromMultipleResponses([]*http.Response{}, func(args ...interface{}) {})
_, err = responder(req)
if err == nil {
t.Error("Error should not be nil")
} else if err.Error() != "not enough responses provided: responder called 1 time(s) but 0 response(s) provided" {
t.Errorf("Invalid error message")
} else if ne, ok := err.(internal.StackTracer); !ok {
t.Errorf(`err type mismatch, got %T, expected internal.StackTracer`, err)
} else if ne.CustomFn == nil {
t.Error(`ne.CustomFn should not be nil`)
}
}

func TestNewNotFoundResponder(t *testing.T) {
responder := NewNotFoundResponder(func(args ...interface{}) {})

Expand Down