Skip to content

Commit

Permalink
Add http debug logging to all requests and responses
Browse files Browse the repository at this point in the history
Useful for debugging tests. Closes #17
  • Loading branch information
Stein Fletcher committed Jan 16, 2019
1 parent 84f2894 commit 5475ae4
Show file tree
Hide file tree
Showing 5 changed files with 102 additions and 51 deletions.
34 changes: 16 additions & 18 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,20 @@ func TestApi(t *testing.T) {
}
```

#### Debugging http requests and responses generated by api test and any mocks

```go
func TestApi(t *testing.T) {
apitest.New().
Debug().
Handler(handler).
Get("/hello").
Expect(t).
Status(http.StatusOK).
End()
}
```

#### Provide basic auth in the request

```go
Expand Down Expand Up @@ -238,8 +252,8 @@ func TestApi(t *testing.T) {
func TestApi(t *testing.T) {
apitest.New().
Observe(func(res *http.Response, req *http.Request) {
// do something with res and req
}).
// do something with res and req
}).
Handler(handler).
Get("/hello").
Expect(t).
Expand All @@ -248,22 +262,6 @@ func TestApi(t *testing.T) {
}
```

one usage for this might be debug logging to the console. The provided `DumpHttp` function does this automatically

```go
func TestApi(t *testing.T) {
apitest.New().
Observe(apitest.DumpHttp).
Handler(handler).
Post("/hello").
Body(`{"a": 12345}`).
Headers(map[string]string{"Content-Type": "application/json"}).
Expect(t).
Status(http.StatusCreated).
End()
}
```

#### Intercept the request

This is useful for mutating the request before it is sent to the system under test.
Expand Down
83 changes: 55 additions & 28 deletions apitest.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,21 @@ import (
"testing"
)

var divider = strings.Repeat("-", 10)
var requestDebugPrefix = fmt.Sprintf("%s>", divider)
var responseDebugPrefix = fmt.Sprintf("<%s", divider)

// APITest is the top level struct holding the test spec
type APITest struct {
name string
request *Request
response *Response
observer Observe
mocks []*Mock
t *testing.T
httpClient *http.Client
transport *Transport
debugEnabled bool
name string
request *Request
response *Response
observers []Observe
mocks []*Mock
t *testing.T
httpClient *http.Client
transport *Transport
}

// Observe will be called by with the request and response on completion
Expand All @@ -42,6 +47,11 @@ func New(name ...string) *APITest {
return apiTest
}

func (a *APITest) Debug() *APITest {
a.debugEnabled = true
return a
}

// Mocks is a builder method for setting the mocks
func (a *APITest) Mocks(mocks ...*Mock) *APITest {
var m []*Mock
Expand All @@ -62,9 +72,9 @@ func (a *APITest) HttpClient(cli *http.Client) *APITest {
return a
}

// Observe is a builder method for setting the observer
func (a *APITest) Observe(observer Observe) *APITest {
a.observer = observer
// Observe is a builder method for setting the observers
func (a *APITest) Observe(observers ...Observe) *APITest {
a.observers = observers
return a
}

Expand Down Expand Up @@ -107,19 +117,6 @@ type pair struct {
r string
}

// DumpHttp logs the http wire representation of the request and response
var DumpHttp Observe = func(res *http.Response, req *http.Request) {
requestDump, err := httputil.DumpRequest(req, true)
if err == nil {
fmt.Println("--> http request dump\n\n" + string(requestDump))
}

responseDump, err := httputil.DumpResponse(res, true)
if err == nil {
fmt.Println("<-- http response dump:\n\n" + string(responseDump))
}
}

// Intercept is a builder method for setting the request interceptor
func (r *Request) Intercept(interceptor Intercept) *Request {
r.interceptor = interceptor
Expand Down Expand Up @@ -284,7 +281,11 @@ func (r *Response) End() {
apiTest := r.apiTest

if len(apiTest.mocks) > 0 {
apiTest.transport = NewTransport(apiTest.mocks, apiTest.httpClient)
apiTest.transport = NewTransport(
apiTest.mocks,
apiTest.httpClient,
r.apiTest.debugEnabled,
)
defer apiTest.transport.Reset()
apiTest.transport.Hijack()
}
Expand All @@ -294,9 +295,15 @@ func (r *Response) End() {

func (a *APITest) run() {
res, req := a.runTest()
if a.observer != nil {
a.observer(res.Result(), req)
}

defer func() {
if len(a.observers) > 0 {
for _, observe := range a.observers {
observe(res.Result(), req)
}
}
}()

a.assertResponse(res)
a.assertHeaders(res)
a.assertCookies(res)
Expand All @@ -315,7 +322,23 @@ func (a *APITest) runTest() (*httptest.ResponseRecorder, *http.Request) {
a.request.interceptor(req)
}
res := httptest.NewRecorder()

if a.debugEnabled {
requestDump, err := httputil.DumpRequest(req, true)
if err == nil {
debug(requestDebugPrefix,"inbound http request", string(requestDump))
}
}

a.request.handler.ServeHTTP(res, req)

if a.debugEnabled {
responseDump, err := httputil.DumpResponse(res.Result(), true)
if err == nil {
debug(responseDebugPrefix,"final response", string(responseDump))
}
}

return res, req
}

Expand Down Expand Up @@ -442,3 +465,7 @@ func isJSON(s string) bool {
var js map[string]interface{}
return json.Unmarshal([]byte(s), &js) == nil
}

func debug(prefix, header, msg string) {
fmt.Printf("\n%s %s\n%s\n", prefix, header, msg)
}
1 change: 0 additions & 1 deletion apitest_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -373,7 +373,6 @@ func TestApiTest_Observe_DumpsTheHttpRequestAndResponse(t *testing.T) {
})

New().
Observe(DumpHttp).
Handler(handler).
Post("/hello").
Body(`{"a": 12345}`).
Expand Down
34 changes: 30 additions & 4 deletions mocks.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"errors"
"io/ioutil"
"net/http"
"net/http/httputil"
"net/url"
"reflect"
"regexp"
Expand All @@ -17,15 +18,17 @@ var (
)

type Transport struct {
debugEnabled bool
mocks []*Mock
nativeTransport http.RoundTripper
httpClient *http.Client
}

func NewTransport(mocks []*Mock, httpClient *http.Client) *Transport {
func NewTransport(mocks []*Mock, httpClient *http.Client, debugEnabled bool) *Transport {
t := &Transport{
mocks: mocks,
httpClient: httpClient,
mocks: mocks,
httpClient: httpClient,
debugEnabled: debugEnabled,
}
if httpClient != nil {
t.nativeTransport = httpClient.Transport
Expand All @@ -36,12 +39,35 @@ func NewTransport(mocks []*Mock, httpClient *http.Client) *Transport {
}

func (r *Transport) RoundTrip(req *http.Request) (*http.Response, error) {
var responseMock *http.Response
if r.debugEnabled {
defer func() {
debugMock(responseMock, req)
}()
}
if matchedResponse := matches(req, r.mocks); matchedResponse != nil {
return buildResponseFromMock(matchedResponse), nil
responseMock = buildResponseFromMock(matchedResponse)
return responseMock, nil
}
return nil, errors.New(ErrFailedToMatch)
}

func debugMock(res *http.Response, req *http.Request) {
requestDump, err := httputil.DumpRequestOut(req, true)
if err == nil {
debug(requestDebugPrefix,"request to mock", string(requestDump))
}

if res != nil {
responseDump, err := httputil.DumpResponse(res, true)
if err == nil {
debug(responseDebugPrefix,"response from mock", string(responseDump))
}
} else {
debug(responseDebugPrefix,"response from mock", "")
}
}

func (r *Transport) Hijack() {
if r.httpClient != nil {
r.httpClient.Transport = r
Expand Down
1 change: 1 addition & 0 deletions mocks_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -338,6 +338,7 @@ func TestMocks_ApiTest_WithMocks(t *testing.T) {
End()

New().
Debug().
HttpClient(test.httpCli).
Mocks(getUser, getPreferences).
Handler(getUserHandler(NewHttpGet(test.httpCli))).
Expand Down

0 comments on commit 5475ae4

Please sign in to comment.