Skip to content

Commit

Permalink
Increase test coverage
Browse files Browse the repository at this point in the history
- Establish testing for httpassert testing.T helpers.
- Allow response header setting in mocked requests.
  • Loading branch information
georgepsarakis committed Oct 2, 2024
1 parent aaf7ec9 commit fb55abf
Show file tree
Hide file tree
Showing 11 changed files with 222 additions and 22 deletions.
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -10,5 +10,6 @@ require (
require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
golang.org/x/sync v0.8.0 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZb
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
golang.org/x/sync v0.8.0 h1:3NFvSEYkUoMifnESzZl15y791HH1qU2xm6eCJU5ZPXQ=
golang.org/x/sync v0.8.0/go.mod h1:Czt+wKu1gCyEFDUtn0jG5QVvpJ6rzVqr5aXyt9drQfk=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down
23 changes: 23 additions & 0 deletions httpassert/debug_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package httpassert

import (
"testing"

"github.com/stretchr/testify/require"
"golang.org/x/sync/errgroup"
)

func TestPrintJSON(t *testing.T) {
t.Parallel()

t.Run("fails if the input cannot be marshalled to JSON", func(t *testing.T) {
grp := errgroup.Group{}
stubTest := &testing.T{}
grp.Go(func() error {
PrintJSON(stubTest, func() {})
return nil
})
require.NoError(t, grp.Wait())
require.True(t, stubTest.Failed())
})
}
26 changes: 16 additions & 10 deletions httpassert/response.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
"bytes"
"io"
"net/http"
"strings"
"testing"

"github.com/stretchr/testify/assert"
Expand All @@ -17,16 +18,21 @@ func ResponseEqual(t *testing.T, actual, expected *http.Response) {
if expected.Header != nil {
assert.Equal(t, expected.Header, actual.Header)
}
expectedBody, err := io.ReadAll(expected.Body)
require.NoError(t, err)
expected.Body.Close()
actualBody, err := io.ReadAll(actual.Body)
require.NoError(t, err)
actual.Body.Close()
// Restore the body stream in order to allow multiple assertions
actual.Body = io.NopCloser(bytes.NewBuffer(actualBody))
assert.JSONEq(t, string(expectedBody), string(actualBody))

if expected.Body != nil {
expectedBody, err := io.ReadAll(expected.Body)
require.NoError(t, err)
expected.Body.Close()
actualBody, err := io.ReadAll(actual.Body)
require.NoError(t, err)
actual.Body.Close()
// Restore the body stream in order to allow multiple assertions
actual.Body = io.NopCloser(bytes.NewBuffer(actualBody))
if strings.HasPrefix(actual.Header.Get("Content-Type"), "application/json") {
assert.JSONEq(t, string(expectedBody), string(actualBody))
} else {
assert.Equal(t, string(expectedBody), string(actualBody))
}
}
if expected.Request != nil {
assert.Equal(t, expected.Request.URL, actual.Request.URL)
assert.Equal(t, expected.Request.Method, actual.Request.Method)
Expand Down
54 changes: 54 additions & 0 deletions httpassert/response_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
package httpassert

import (
"net/http"
"testing"

"github.com/stretchr/testify/assert"
)

func TestResponseEqual(t *testing.T) {
type args struct {
t *testing.T
actual *http.Response
expected *http.Response
}
tests := []struct {
name string
args args
want assert.BoolAssertionFunc
}{
{
name: "status code does not match",
args: args{
t: &testing.T{},
actual: &http.Response{
StatusCode: http.StatusBadRequest,
},
expected: &http.Response{
StatusCode: http.StatusOK,
},
},
want: assert.True,
},
{
name: "status code matches",
args: args{
t: &testing.T{},
actual: &http.Response{
StatusCode: http.StatusBadRequest,
},
expected: &http.Response{
StatusCode: http.StatusBadRequest,
},
},
want: assert.False,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
ResponseEqual(tt.args.t, tt.args.actual, tt.args.expected)
tt.want(t, tt.args.t.Failed())
})
}
}
9 changes: 9 additions & 0 deletions httptesting/client.go
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,15 @@ func (r *MockRequest) RespondWithJSON(statusCode int, body string) *MockRequest
return r
}

func (r *MockRequest) RespondWithHeaders(respHeaders map[string]string) *MockRequest {
h := http.Header{}
for k, v := range respHeaders {
h.Set(k, v)
}
r.responder.HeaderSet(h)
return r
}

func (c *Client) NewJSONBodyMatcher(body string) httpmock.MatcherFunc {
c.t.Helper()

Expand Down
6 changes: 4 additions & 2 deletions httptesting/client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -44,13 +44,15 @@ func TestClient_Head(t *testing.T) {
c.Client.WithDefaultHeaders(map[string]string{"Content-Type": "application/json"})
c.NewMockRequest(http.MethodHead, requestURL+"?test=1",
httpclient.WithHeaders(map[string]string{"Content-Type": "application/json"})).
RespondWithJSON(http.StatusOK, `{"name": "hello", "surname": "world"}`).Register()
RespondWithJSON(http.StatusOK, `{"name": "hello", "surname": "world"}`).
RespondWithHeaders(map[string]string{"Content-Type": "application/json"}).
Register()
resp, err := c.Head(context.Background(),
requestURL,
httpclient.WithQueryParameters(map[string]string{"test": "1"}))
require.NoError(t, err)
reqHeaders := http.Header{}
reqHeaders.Set("Content-Type", "application/json")
reqHeaders.Set("Accept", "application/json")
httpassert.ResponseEqual(t, resp, &http.Response{
StatusCode: http.StatusOK,
Request: &http.Request{
Expand Down
10 changes: 2 additions & 8 deletions integration/client_get_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@ package integration

import (
"context"
"encoding/json"
"testing"

"github.com/stretchr/testify/require"

"github.com/georgepsarakis/go-httpclient"
"github.com/georgepsarakis/go-httpclient/httpassert"
)

func TestClient_Get_JSON(t *testing.T) {
Expand All @@ -16,7 +16,7 @@ func TestClient_Get_JSON(t *testing.T) {
require.NoError(t, err)
v := map[string]interface{}{}
require.NoError(t, httpclient.DeserializeJSON(resp, &v))
printJSON(t, v)
httpassert.PrintJSON(t, v)
}

func githubClient(t *testing.T) *httpclient.Client {
Expand All @@ -28,12 +28,6 @@ func githubClient(t *testing.T) *httpclient.Client {
return c
}

func printJSON(t *testing.T, v any) {
b, err := json.MarshalIndent(v, "", " ")
require.NoError(t, err)
t.Log(string(b))
}

func TestClient_Get_JSON_ContextDeadline(t *testing.T) {
c := githubClient(t)

Expand Down
6 changes: 4 additions & 2 deletions request.go
Original file line number Diff line number Diff line change
Expand Up @@ -70,12 +70,14 @@ func InterceptRequestBody(r *http.Request) ([]byte, error) {
if err != nil {
return nil, err
}
r.Body.Close()
if err := r.Body.Close(); err != nil {
return nil, err
}
r.Body = io.NopCloser(bytes.NewReader(body))
return body, nil
}

func MustInterceptRequestBody(r *http.Request, body []byte) []byte {
func MustInterceptRequestBody(r *http.Request) []byte {
b, err := InterceptRequestBody(r)
if err != nil {
panic(err)
Expand Down
43 changes: 43 additions & 0 deletions request_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
package httpclient

import (
"io"
"net/http"
"net/url"
"strings"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

func TestWithQueryParameters(t *testing.T) {
Expand Down Expand Up @@ -57,3 +61,42 @@ func TestWithQueryParameters(t *testing.T) {
})
}
}

func TestMustInterceptRequestBody(t *testing.T) {
require.Panics(t, func() {
MustInterceptRequestBody(&http.Request{Body: failureOnReadReader{}})
})
require.Panics(t, func() {
MustInterceptRequestBody(&http.Request{Body: failureOnCloseReader{}})
})

req := &http.Request{Body: io.NopCloser(strings.NewReader("test"))}
require.Equal(t, []byte("test"), MustInterceptRequestBody(req))
b, err := io.ReadAll(req.Body)
require.NoError(t, err)
require.Equal(t, []byte("test"), b)
}

type failureOnReadReader struct {
io.ReadCloser
}

func (f failureOnReadReader) Read(_ []byte) (n int, err error) {
return 0, io.ErrUnexpectedEOF
}

func (f failureOnReadReader) Close() error {
return nil
}

type failureOnCloseReader struct {
io.ReadCloser
}

func (f failureOnCloseReader) Read(_ []byte) (n int, err error) {
return 0, io.EOF
}

func (f failureOnCloseReader) Close() error {
return io.ErrClosedPipe
}
64 changes: 64 additions & 0 deletions response_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package httpclient

import (
"io"
"net/http"
"strings"
"testing"

"github.com/stretchr/testify/assert"
)

func TestDeserializeJSON(t *testing.T) {
type args struct {
resp *http.Response
target any
}
tests := []struct {
name string
args args
wantErrMessage string
want map[string]any
}{
{
name: "returns error from JSON marshalling",
args: args{
resp: &http.Response{
Body: io.NopCloser(strings.NewReader("{")),
},
target: &map[string]any{},
},
wantErrMessage: "unexpected end of JSON input",
},
{
name: "returns error when not passing a pointer",
args: args{
resp: &http.Response{
Body: io.NopCloser(strings.NewReader("{}")),
},
target: map[string]any{},
},
wantErrMessage: "pointer required, got map[string]interface {}",
},
{
name: "unmarshals the JSON payload to the passed pointer",
args: args{
resp: &http.Response{
Body: io.NopCloser(strings.NewReader(`{"hello": "world"}`)),
},
target: &map[string]any{},
},
want: map[string]any{"hello": "world"},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
err := DeserializeJSON(tt.args.resp, tt.args.target)
if tt.wantErrMessage != "" {
assert.ErrorContains(t, err, tt.wantErrMessage)
} else {
assert.Equal(t, tt.want, *tt.args.target.(*map[string]any))
}
})
}
}

0 comments on commit fb55abf

Please sign in to comment.