Skip to content

Commit fb55abf

Browse files
Increase test coverage
- Establish testing for httpassert testing.T helpers. - Allow response header setting in mocked requests.
1 parent aaf7ec9 commit fb55abf

11 files changed

+222
-22
lines changed

go.mod

+1
Original file line numberDiff line numberDiff line change
@@ -10,5 +10,6 @@ require (
1010
require (
1111
github.com/davecgh/go-spew v1.1.1 // indirect
1212
github.com/pmezard/go-difflib v1.0.0 // indirect
13+
golang.org/x/sync v0.8.0 // indirect
1314
gopkg.in/yaml.v3 v3.0.1 // indirect
1415
)

go.sum

+2
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
88
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
99
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
1010
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
11+
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
12+
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
1113
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
1214
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
1315
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=

httpassert/debug_test.go

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package httpassert
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/require"
7+
"golang.org/x/sync/errgroup"
8+
)
9+
10+
func TestPrintJSON(t *testing.T) {
11+
t.Parallel()
12+
13+
t.Run("fails if the input cannot be marshalled to JSON", func(t *testing.T) {
14+
grp := errgroup.Group{}
15+
stubTest := &testing.T{}
16+
grp.Go(func() error {
17+
PrintJSON(stubTest, func() {})
18+
return nil
19+
})
20+
require.NoError(t, grp.Wait())
21+
require.True(t, stubTest.Failed())
22+
})
23+
}

httpassert/response.go

+16-10
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"bytes"
55
"io"
66
"net/http"
7+
"strings"
78
"testing"
89

910
"github.com/stretchr/testify/assert"
@@ -17,16 +18,21 @@ func ResponseEqual(t *testing.T, actual, expected *http.Response) {
1718
if expected.Header != nil {
1819
assert.Equal(t, expected.Header, actual.Header)
1920
}
20-
expectedBody, err := io.ReadAll(expected.Body)
21-
require.NoError(t, err)
22-
expected.Body.Close()
23-
actualBody, err := io.ReadAll(actual.Body)
24-
require.NoError(t, err)
25-
actual.Body.Close()
26-
// Restore the body stream in order to allow multiple assertions
27-
actual.Body = io.NopCloser(bytes.NewBuffer(actualBody))
28-
assert.JSONEq(t, string(expectedBody), string(actualBody))
29-
21+
if expected.Body != nil {
22+
expectedBody, err := io.ReadAll(expected.Body)
23+
require.NoError(t, err)
24+
expected.Body.Close()
25+
actualBody, err := io.ReadAll(actual.Body)
26+
require.NoError(t, err)
27+
actual.Body.Close()
28+
// Restore the body stream in order to allow multiple assertions
29+
actual.Body = io.NopCloser(bytes.NewBuffer(actualBody))
30+
if strings.HasPrefix(actual.Header.Get("Content-Type"), "application/json") {
31+
assert.JSONEq(t, string(expectedBody), string(actualBody))
32+
} else {
33+
assert.Equal(t, string(expectedBody), string(actualBody))
34+
}
35+
}
3036
if expected.Request != nil {
3137
assert.Equal(t, expected.Request.URL, actual.Request.URL)
3238
assert.Equal(t, expected.Request.Method, actual.Request.Method)

httpassert/response_test.go

+54
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package httpassert
2+
3+
import (
4+
"net/http"
5+
"testing"
6+
7+
"github.com/stretchr/testify/assert"
8+
)
9+
10+
func TestResponseEqual(t *testing.T) {
11+
type args struct {
12+
t *testing.T
13+
actual *http.Response
14+
expected *http.Response
15+
}
16+
tests := []struct {
17+
name string
18+
args args
19+
want assert.BoolAssertionFunc
20+
}{
21+
{
22+
name: "status code does not match",
23+
args: args{
24+
t: &testing.T{},
25+
actual: &http.Response{
26+
StatusCode: http.StatusBadRequest,
27+
},
28+
expected: &http.Response{
29+
StatusCode: http.StatusOK,
30+
},
31+
},
32+
want: assert.True,
33+
},
34+
{
35+
name: "status code matches",
36+
args: args{
37+
t: &testing.T{},
38+
actual: &http.Response{
39+
StatusCode: http.StatusBadRequest,
40+
},
41+
expected: &http.Response{
42+
StatusCode: http.StatusBadRequest,
43+
},
44+
},
45+
want: assert.False,
46+
},
47+
}
48+
for _, tt := range tests {
49+
t.Run(tt.name, func(t *testing.T) {
50+
ResponseEqual(tt.args.t, tt.args.actual, tt.args.expected)
51+
tt.want(t, tt.args.t.Failed())
52+
})
53+
}
54+
}

httptesting/client.go

+9
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,15 @@ func (r *MockRequest) RespondWithJSON(statusCode int, body string) *MockRequest
126126
return r
127127
}
128128

129+
func (r *MockRequest) RespondWithHeaders(respHeaders map[string]string) *MockRequest {
130+
h := http.Header{}
131+
for k, v := range respHeaders {
132+
h.Set(k, v)
133+
}
134+
r.responder.HeaderSet(h)
135+
return r
136+
}
137+
129138
func (c *Client) NewJSONBodyMatcher(body string) httpmock.MatcherFunc {
130139
c.t.Helper()
131140

httptesting/client_test.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -44,13 +44,15 @@ func TestClient_Head(t *testing.T) {
4444
c.Client.WithDefaultHeaders(map[string]string{"Content-Type": "application/json"})
4545
c.NewMockRequest(http.MethodHead, requestURL+"?test=1",
4646
httpclient.WithHeaders(map[string]string{"Content-Type": "application/json"})).
47-
RespondWithJSON(http.StatusOK, `{"name": "hello", "surname": "world"}`).Register()
47+
RespondWithJSON(http.StatusOK, `{"name": "hello", "surname": "world"}`).
48+
RespondWithHeaders(map[string]string{"Content-Type": "application/json"}).
49+
Register()
4850
resp, err := c.Head(context.Background(),
4951
requestURL,
5052
httpclient.WithQueryParameters(map[string]string{"test": "1"}))
5153
require.NoError(t, err)
5254
reqHeaders := http.Header{}
53-
reqHeaders.Set("Content-Type", "application/json")
55+
reqHeaders.Set("Accept", "application/json")
5456
httpassert.ResponseEqual(t, resp, &http.Response{
5557
StatusCode: http.StatusOK,
5658
Request: &http.Request{

integration/client_get_test.go

+2-8
Original file line numberDiff line numberDiff line change
@@ -2,12 +2,12 @@ package integration
22

33
import (
44
"context"
5-
"encoding/json"
65
"testing"
76

87
"github.com/stretchr/testify/require"
98

109
"github.com/georgepsarakis/go-httpclient"
10+
"github.com/georgepsarakis/go-httpclient/httpassert"
1111
)
1212

1313
func TestClient_Get_JSON(t *testing.T) {
@@ -16,7 +16,7 @@ func TestClient_Get_JSON(t *testing.T) {
1616
require.NoError(t, err)
1717
v := map[string]interface{}{}
1818
require.NoError(t, httpclient.DeserializeJSON(resp, &v))
19-
printJSON(t, v)
19+
httpassert.PrintJSON(t, v)
2020
}
2121

2222
func githubClient(t *testing.T) *httpclient.Client {
@@ -28,12 +28,6 @@ func githubClient(t *testing.T) *httpclient.Client {
2828
return c
2929
}
3030

31-
func printJSON(t *testing.T, v any) {
32-
b, err := json.MarshalIndent(v, "", " ")
33-
require.NoError(t, err)
34-
t.Log(string(b))
35-
}
36-
3731
func TestClient_Get_JSON_ContextDeadline(t *testing.T) {
3832
c := githubClient(t)
3933

request.go

+4-2
Original file line numberDiff line numberDiff line change
@@ -70,12 +70,14 @@ func InterceptRequestBody(r *http.Request) ([]byte, error) {
7070
if err != nil {
7171
return nil, err
7272
}
73-
r.Body.Close()
73+
if err := r.Body.Close(); err != nil {
74+
return nil, err
75+
}
7476
r.Body = io.NopCloser(bytes.NewReader(body))
7577
return body, nil
7678
}
7779

78-
func MustInterceptRequestBody(r *http.Request, body []byte) []byte {
80+
func MustInterceptRequestBody(r *http.Request) []byte {
7981
b, err := InterceptRequestBody(r)
8082
if err != nil {
8183
panic(err)

request_test.go

+43
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
11
package httpclient
22

33
import (
4+
"io"
5+
"net/http"
46
"net/url"
7+
"strings"
58
"testing"
69

710
"github.com/stretchr/testify/assert"
11+
"github.com/stretchr/testify/require"
812
)
913

1014
func TestWithQueryParameters(t *testing.T) {
@@ -57,3 +61,42 @@ func TestWithQueryParameters(t *testing.T) {
5761
})
5862
}
5963
}
64+
65+
func TestMustInterceptRequestBody(t *testing.T) {
66+
require.Panics(t, func() {
67+
MustInterceptRequestBody(&http.Request{Body: failureOnReadReader{}})
68+
})
69+
require.Panics(t, func() {
70+
MustInterceptRequestBody(&http.Request{Body: failureOnCloseReader{}})
71+
})
72+
73+
req := &http.Request{Body: io.NopCloser(strings.NewReader("test"))}
74+
require.Equal(t, []byte("test"), MustInterceptRequestBody(req))
75+
b, err := io.ReadAll(req.Body)
76+
require.NoError(t, err)
77+
require.Equal(t, []byte("test"), b)
78+
}
79+
80+
type failureOnReadReader struct {
81+
io.ReadCloser
82+
}
83+
84+
func (f failureOnReadReader) Read(_ []byte) (n int, err error) {
85+
return 0, io.ErrUnexpectedEOF
86+
}
87+
88+
func (f failureOnReadReader) Close() error {
89+
return nil
90+
}
91+
92+
type failureOnCloseReader struct {
93+
io.ReadCloser
94+
}
95+
96+
func (f failureOnCloseReader) Read(_ []byte) (n int, err error) {
97+
return 0, io.EOF
98+
}
99+
100+
func (f failureOnCloseReader) Close() error {
101+
return io.ErrClosedPipe
102+
}

response_test.go

+64
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
package httpclient
2+
3+
import (
4+
"io"
5+
"net/http"
6+
"strings"
7+
"testing"
8+
9+
"github.com/stretchr/testify/assert"
10+
)
11+
12+
func TestDeserializeJSON(t *testing.T) {
13+
type args struct {
14+
resp *http.Response
15+
target any
16+
}
17+
tests := []struct {
18+
name string
19+
args args
20+
wantErrMessage string
21+
want map[string]any
22+
}{
23+
{
24+
name: "returns error from JSON marshalling",
25+
args: args{
26+
resp: &http.Response{
27+
Body: io.NopCloser(strings.NewReader("{")),
28+
},
29+
target: &map[string]any{},
30+
},
31+
wantErrMessage: "unexpected end of JSON input",
32+
},
33+
{
34+
name: "returns error when not passing a pointer",
35+
args: args{
36+
resp: &http.Response{
37+
Body: io.NopCloser(strings.NewReader("{}")),
38+
},
39+
target: map[string]any{},
40+
},
41+
wantErrMessage: "pointer required, got map[string]interface {}",
42+
},
43+
{
44+
name: "unmarshals the JSON payload to the passed pointer",
45+
args: args{
46+
resp: &http.Response{
47+
Body: io.NopCloser(strings.NewReader(`{"hello": "world"}`)),
48+
},
49+
target: &map[string]any{},
50+
},
51+
want: map[string]any{"hello": "world"},
52+
},
53+
}
54+
for _, tt := range tests {
55+
t.Run(tt.name, func(t *testing.T) {
56+
err := DeserializeJSON(tt.args.resp, tt.args.target)
57+
if tt.wantErrMessage != "" {
58+
assert.ErrorContains(t, err, tt.wantErrMessage)
59+
} else {
60+
assert.Equal(t, tt.want, *tt.args.target.(*map[string]any))
61+
}
62+
})
63+
}
64+
}

0 commit comments

Comments
 (0)