Skip to content

Commit ac7b6af

Browse files
authored
Merge pull request #17 from dvic/json-generic-assertion
support asserting JSON types other than objects (fixes #16)
2 parents ea5c148 + 3591283 commit ac7b6af

File tree

2 files changed

+103
-44
lines changed

2 files changed

+103
-44
lines changed

assert/json.go

Lines changed: 32 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -4,21 +4,20 @@ import (
44
"bytes"
55
"encoding/json"
66
"fmt"
7-
"io"
87
"io/ioutil"
98
"net/http"
109
"reflect"
1110
)
1211

13-
func unmarshal(buf []byte) (map[string]interface{}, error) {
14-
data := make(map[string]interface{})
12+
func unmarshal(buf []byte) (interface{}, error) {
13+
var data interface{}
1514
if err := json.Unmarshal(buf, &data); err != nil {
1615
return data, fmt.Errorf("failed to Unmarshal: %s", err)
1716
}
1817
return data, nil
1918
}
2019

21-
func unmarshalBody(res *http.Response) (map[string]interface{}, error) {
20+
func unmarshalBody(res *http.Response) (interface{}, error) {
2221
body, err := ioutil.ReadAll(res.Body)
2322
if err != nil {
2423
return nil, err
@@ -29,7 +28,7 @@ func unmarshalBody(res *http.Response) (map[string]interface{}, error) {
2928
return unmarshal(body)
3029
}
3130

32-
func marshal(data map[string]interface{}) ([]byte, error) {
31+
func marshal(data interface{}) ([]byte, error) {
3332
return json.Marshal(data)
3433
}
3534

@@ -44,53 +43,48 @@ func readBodyJSON(res *http.Response) ([]byte, error) {
4443
return body, err
4544
}
4645

47-
func compare(body map[string]interface{}, data interface{}) error {
46+
func compare(body interface{}, data interface{}) error {
4847
var err error
49-
var match map[string]interface{}
50-
51-
// Infer and cast string input
52-
if jsonStr, ok := data.(string); ok {
53-
data = []byte(jsonStr)
48+
var matchBytes []byte
49+
50+
// taking pointer to json.RawMessage due to regression in go 1.7 (https://github.com/golang/go/issues/14493)
51+
var matchData interface{}
52+
switch data := data.(type) {
53+
case json.Marshaler:
54+
matchData = data
55+
case string:
56+
v := json.RawMessage([]byte(data))
57+
matchData = &v
58+
case []byte:
59+
v := json.RawMessage(data)
60+
matchData = &v
61+
default:
62+
matchData = data
5463
}
64+
matchBytes, err = marshal(matchData)
5565

56-
// Infer and cast string input
57-
if reader, ok := data.(io.Reader); ok {
58-
data, err = ioutil.ReadAll(reader)
59-
if err != nil {
60-
return err
61-
}
62-
}
63-
64-
// Infer and cast input as map
65-
if fields, ok := data.(map[string]interface{}); ok {
66-
data, err = marshal(fields)
67-
if err != nil {
68-
return err
69-
}
66+
if err != nil {
67+
return err
7068
}
7169

72-
// Infer and cast bytes input
73-
buf, ok := data.([]byte)
74-
if ok {
75-
match, err = unmarshal(buf)
76-
if err != nil {
77-
return err
78-
}
70+
// Assert via string
71+
bodyBytes, err := marshal(body)
72+
if err != nil {
73+
return err
7974
}
8075

81-
// Assert via string
82-
bodyBuf, err := marshal(body)
76+
bodyValue, err := unmarshal(bodyBytes)
8377
if err != nil {
8478
return err
8579
}
86-
matchBuf, err := marshal(match)
80+
matchValue, err := unmarshal(matchBytes)
8781
if err != nil {
8882
return err
8983
}
9084

91-
// Compare by byte sequences
92-
if !reflect.DeepEqual(bodyBuf, matchBuf) {
93-
return fmt.Errorf("failed due to JSON mismatch:\n\thave: %#v\n\twant: %#v", string(bodyBuf), string(matchBuf))
85+
// Compare values so order of keys in maps does not influence the result
86+
if !reflect.DeepEqual(bodyValue, matchValue) {
87+
return fmt.Errorf("failed due to JSON mismatch:\n\thave: %#v\n\twant: %#v", string(bodyBytes), string(matchBytes))
9488
}
9589

9690
return nil

assert/json_test.go

Lines changed: 71 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,77 @@ func TestJSONBuffer(t *testing.T) {
2525
st.Expect(t, JSON(match)(res, nil), nil)
2626
}
2727

28-
func TestJSONMap(t *testing.T) {
29-
body := ioutil.NopCloser(bytes.NewBufferString(`{"foo": [{"bar":"baz"}]}`))
30-
res := &http.Response{Body: body}
31-
list := []items{{"bar": "baz"}}
32-
match := map[string]interface{}{"foo": list}
33-
st.Expect(t, JSON(match)(res, nil), nil)
28+
type literalJSON string
29+
30+
func (v literalJSON) MarshalJSON() ([]byte, error) {
31+
return []byte(v), nil
32+
}
33+
34+
func TestJSON(t *testing.T) {
35+
type genMap map[string]interface{}
36+
type strMap map[string]string
37+
testcases := []struct {
38+
name string
39+
body string
40+
match interface{}
41+
}{
42+
{
43+
name: "generic map",
44+
body: `{"foo": [{"bar":"baz"}]}`,
45+
match: genMap{"foo": []items{{"bar": "baz"}}},
46+
},
47+
{
48+
name: "string to sring map",
49+
body: `{"foo": "bar"}`,
50+
match: strMap{"foo": "bar"},
51+
},
52+
{
53+
name: "array",
54+
body: `["foo", 1.0]`,
55+
match: []interface{}{"foo", 1.0},
56+
},
57+
{
58+
name: "custom type with json marshaller",
59+
body: `"foo"`,
60+
match: literalJSON(`"foo"`),
61+
},
62+
{
63+
name: "keys order in map does not matter",
64+
body: `
65+
{
66+
"args": {},
67+
"headers": {
68+
"Accept-Encoding": "gzip",
69+
"Connection": "close",
70+
"Foo": "Bar",
71+
"Host": "httpbin.org",
72+
"User-Agent": "baloo/2.0.0"
73+
},
74+
"origin": "0.0.0.0",
75+
"url": "http://httpbin.org/get"
76+
}`,
77+
match: literalJSON(`
78+
{
79+
"args": {},
80+
"headers": {
81+
"Connection": "close",
82+
"Accept-Encoding": "gzip",
83+
"Foo": "Bar",
84+
"Host": "httpbin.org",
85+
"User-Agent": "baloo/2.0.0"
86+
},
87+
"origin": "0.0.0.0",
88+
"url": "http://httpbin.org/get"
89+
}`),
90+
},
91+
}
92+
for i, tc := range testcases {
93+
t.Run(tc.name, func(t *testing.T) {
94+
body := ioutil.NopCloser(bytes.NewBufferString(tc.body))
95+
res := &http.Response{Body: body}
96+
st.Expect(t, JSON(tc.match)(res, nil), nil, i)
97+
})
98+
}
3499
}
35100

36101
func TestCompare(t *testing.T) {

0 commit comments

Comments
 (0)