From 930ea90dbd163f5a9e1452b2eb60be94c5cbf277 Mon Sep 17 00:00:00 2001 From: Julian Cooper Date: Wed, 23 Sep 2015 10:34:52 -0700 Subject: [PATCH] Added assertion/requirement that checks if two JSON strings represent equivalent objects --- assert/assertions.go | 35 ++++++++++++++++++ assert/assertions_test.go | 17 ++++++++- assert/forward_assertions.go | 9 +++++ assert/forward_assertions_test.go | 41 +++++++++++++++++++++ require/forward_requirements.go | 9 +++++ require/forward_requirements_test.go | 53 ++++++++++++++++++++++++++++ require/requirements.go | 35 ++++++++++++++++++ require/requirements_test.go | 50 ++++++++++++++++++++++++++ 8 files changed, 248 insertions(+), 1 deletion(-) diff --git a/assert/assertions.go b/assert/assertions.go index 023b1c3..b4d863f 100644 --- a/assert/assertions.go +++ b/assert/assertions.go @@ -3,6 +3,7 @@ package assert import ( "bufio" "bytes" + "encoding/json" "fmt" "math" "reflect" @@ -909,3 +910,37 @@ func NotZero(t TestingT, i interface{}, msgAndArgs ...interface{}) bool { } return true } + +// JSONEq asserts that two JSON strings are equivalent. +// +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// +// Returns whether the assertion was successful (true) or not (false). +func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool { + expectSlice := false + expectedJSONAsMap := make(map[string]interface{}) + expectedJSONAsSlice := make([]interface{}, 0, 0) + + actualJSONAsMap := make(map[string]interface{}) + actualJSONAsSlice := make([]interface{}, 0, 0) + + if err := json.Unmarshal([]byte(expected), &expectedJSONAsMap); err != nil { + if err := json.Unmarshal([]byte(expected), &expectedJSONAsSlice); err == nil { + expectSlice = true + } else { + return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid json.\nJSON parsing error: '%s'", expected, err.Error()), msgAndArgs...) + } + } + + if expectSlice { + if err := json.Unmarshal([]byte(actual), &actualJSONAsSlice); err != nil { + return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid json.\nJSON parsing error: '%s'", actual, err.Error()), msgAndArgs...) + } + return Equal(t, expectedJSONAsSlice, actualJSONAsSlice, msgAndArgs...) + } else { + if err := json.Unmarshal([]byte(actual), &actualJSONAsMap); err != nil { + return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid json.\nJSON parsing error: '%s'", actual, err.Error()), msgAndArgs...) + } + return Equal(t, expectedJSONAsMap, actualJSONAsMap, msgAndArgs...) + } +} diff --git a/assert/assertions_test.go b/assert/assertions_test.go index ce4ecae..a8daded 100644 --- a/assert/assertions_test.go +++ b/assert/assertions_test.go @@ -11,7 +11,7 @@ import ( ) var ( - i interface{} + i interface{} zeros = []interface{}{ false, byte(0), @@ -903,3 +903,18 @@ func TestNotZero(t *testing.T) { True(t, NotZero(mockT, test, "%#v is not the %v zero value", test, reflect.TypeOf(test))) } } + +func TestJSONEq(t *testing.T) { + mockT := new(testing.T) + + True(t, JSONEq(mockT, `{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`)) + True(t, JSONEq(mockT, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)) + True(t, JSONEq(mockT, "{\r\n\t\"numeric\": 1.5,\r\n\t\"array\": [{\"foo\": \"bar\"}, 1, \"string\", [\"nested\", \"array\", 5.5]],\r\n\t\"hash\": {\"nested\": \"hash\", \"nested_slice\": [\"this\", \"is\", \"nested\"]},\r\n\t\"string\": \"foo\"\r\n}", + "{\r\n\t\"numeric\": 1.5,\r\n\t\"hash\": {\"nested\": \"hash\", \"nested_slice\": [\"this\", \"is\", \"nested\"]},\r\n\t\"string\": \"foo\",\r\n\t\"array\": [{\"foo\": \"bar\"}, 1, \"string\", [\"nested\", \"array\", 5.5]]\r\n}")) + True(t, JSONEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `["foo", {"nested": "hash", "hello": "world"}]`)) + False(t, JSONEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`)) + False(t, JSONEq(mockT, `{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`)) + False(t, JSONEq(mockT, `{"foo": "bar"}`, "Not JSON")) + False(t, JSONEq(mockT, "Not JSON", `{"foo": "bar", "hello": "world"}`)) + False(t, JSONEq(mockT, "Not JSON", "Not JSON")) +} diff --git a/assert/forward_assertions.go b/assert/forward_assertions.go index dc14771..3a21ee9 100644 --- a/assert/forward_assertions.go +++ b/assert/forward_assertions.go @@ -273,3 +273,12 @@ func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) bool { func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) bool { return NotZero(a.t, i, msgAndArgs...) } + +// JSONEq asserts that two JSON strings are equivalent. +// +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) bool { + return JSONEq(a.t, expected, actual, msgAndArgs...) +} diff --git a/assert/forward_assertions_test.go b/assert/forward_assertions_test.go index 280d0ab..3678ae7 100644 --- a/assert/forward_assertions_test.go +++ b/assert/forward_assertions_test.go @@ -535,3 +535,44 @@ func TestNotZeroWrapper(t *testing.T) { assert.True(mockAssert.NotZero(test), "Zero should return false for %v", test) } } + +func TestJSONEqWrapper(t *testing.T) { + assert := New(new(testing.T)) + + if !assert.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`) { + t.Error("JSONEq should return true") + } + + if !assert.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) { + t.Error("JSONEq should return true") + } + + if !assert.JSONEq("{\r\n\t\"numeric\": 1.5,\r\n\t\"array\": [{\"foo\": \"bar\"}, 1, \"string\", [\"nested\", \"array\", 5.5]],\r\n\t\"hash\": {\"nested\": \"hash\", \"nested_slice\": [\"this\", \"is\", \"nested\"]},\r\n\t\"string\": \"foo\"\r\n}", + "{\r\n\t\"numeric\": 1.5,\r\n\t\"hash\": {\"nested\": \"hash\", \"nested_slice\": [\"this\", \"is\", \"nested\"]},\r\n\t\"string\": \"foo\",\r\n\t\"array\": [{\"foo\": \"bar\"}, 1, \"string\", [\"nested\", \"array\", 5.5]]\r\n}") { + t.Error("JSONEq should return true") + } + + if !assert.JSONEq(`["foo", {"hello": "world", "nested": "hash"}]`, `["foo", {"nested": "hash", "hello": "world"}]`) { + t.Error("JSONEq should return true") + } + + if assert.JSONEq(`["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`) { + t.Error("JSONEq should return false") + } + + if assert.JSONEq(`{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) { + t.Error("JSONEq should return false") + } + + if assert.JSONEq(`{"foo": "bar"}`, "Not JSON") { + t.Error("JSONEq should return false for invalid JSON") + } + + if assert.JSONEq("Not JSON", `{"foo": "bar", "hello": "world"}`) { + t.Error("JSONEq should return false") + } + + if assert.JSONEq("Not JSON", "Not JSON") { + t.Error("JSONEq should return false") + } +} diff --git a/require/forward_requirements.go b/require/forward_requirements.go index d58512f..035ecee 100644 --- a/require/forward_requirements.go +++ b/require/forward_requirements.go @@ -219,3 +219,12 @@ func (a *Assertions) Zero(i interface{}, msgAndArgs ...interface{}) { func (a *Assertions) NotZero(i interface{}, msgAndArgs ...interface{}) { NotZero(a.t, i, msgAndArgs...) } + +// JSONEq asserts that two JSON strings are equivalent. +// +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// +// Returns whether the assertion was successful (true) or not (false). +func (a *Assertions) JSONEq(expected string, actual string, msgAndArgs ...interface{}) { + JSONEq(a.t, expected, actual, msgAndArgs...) +} diff --git a/require/forward_requirements_test.go b/require/forward_requirements_test.go index 404962d..475c63b 100644 --- a/require/forward_requirements_test.go +++ b/require/forward_requirements_test.go @@ -282,3 +282,56 @@ func TestNotZeroWrapper(t *testing.T) { t.Error("Check should fail") } } + +func TestJSONEqWrapper(t *testing.T) { + // mockRequire := New(new(testing.T)) + + mockT := new(MockT) + mockRequire := New(mockT) + + mockRequire.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`) + if mockT.Failed { + t.Error("JSONEq should pass") + } + + mockRequire.JSONEq(`{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) + if mockT.Failed { + t.Error("JSONEq should pass") + } + + mockRequire.JSONEq("{\r\n\t\"numeric\": 1.5,\r\n\t\"array\": [{\"foo\": \"bar\"}, 1, \"string\", [\"nested\", \"array\", 5.5]],\r\n\t\"hash\": {\"nested\": \"hash\", \"nested_slice\": [\"this\", \"is\", \"nested\"]},\r\n\t\"string\": \"foo\"\r\n}", + "{\r\n\t\"numeric\": 1.5,\r\n\t\"hash\": {\"nested\": \"hash\", \"nested_slice\": [\"this\", \"is\", \"nested\"]},\r\n\t\"string\": \"foo\",\r\n\t\"array\": [{\"foo\": \"bar\"}, 1, \"string\", [\"nested\", \"array\", 5.5]]\r\n}") + if mockT.Failed { + t.Error("JSONEq should pass") + } + + mockRequire.JSONEq(`["foo", {"hello": "world", "nested": "hash"}]`, `["foo", {"nested": "hash", "hello": "world"}]`) + if mockT.Failed { + t.Error("JSONEq should pass") + } + + mockRequire.JSONEq(`["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`) + if !mockT.Failed { + t.Error("JSONEq should fail") + } + + mockRequire.JSONEq(`{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) + if !mockT.Failed { + t.Error("JSONEq should fail") + } + + mockRequire.JSONEq(`{"foo": "bar"}`, "Not JSON") + if !mockT.Failed { + t.Error("JSONEq should fail") + } + + mockRequire.JSONEq("Not JSON", `{"foo": "bar", "hello": "world"}`) + if !mockT.Failed { + t.Error("JSONEq should fail") + } + + mockRequire.JSONEq("Not JSON", "Not JSON") + if !mockT.Failed { + t.Error("JSONEq should fail") + } +} diff --git a/require/requirements.go b/require/requirements.go index cfc1887..9de321f 100644 --- a/require/requirements.go +++ b/require/requirements.go @@ -1,6 +1,7 @@ package require import ( + "encoding/json" "time" "github.com/stretchr/testify/assert" @@ -228,6 +229,40 @@ func NotRegexp(t TestingT, rx interface{}, str interface{}, msgAndArgs ...interf } } +// JSONEq asserts that two JSON strings are equivalent. +// +// assert.JSONEq(t, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) +// +// Returns whether the assertion was successful (true) or not (false). +func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) { + expectSlice := false + expectedJSONAsMap := make(map[string]interface{}) + expectedJSONAsSlice := make([]interface{}, 0, 0) + + actualJSONAsMap := make(map[string]interface{}) + actualJSONAsSlice := make([]interface{}, 0, 0) + + if err := json.Unmarshal([]byte(expected), &expectedJSONAsMap); err != nil { + if err := json.Unmarshal([]byte(expected), &expectedJSONAsSlice); err == nil { + expectSlice = true + } else { + t.FailNow() + } + } + + if expectSlice { + if err := json.Unmarshal([]byte(actual), &actualJSONAsSlice); err != nil { + t.FailNow() + } + Equal(t, expectedJSONAsSlice, actualJSONAsSlice, msgAndArgs...) + } else { + if err := json.Unmarshal([]byte(actual), &actualJSONAsMap); err != nil { + t.FailNow() + } + Equal(t, expectedJSONAsMap, actualJSONAsMap, msgAndArgs...) + } +} + /* Errors */ diff --git a/require/requirements_test.go b/require/requirements_test.go index 2c9d351..dcc56e0 100644 --- a/require/requirements_test.go +++ b/require/requirements_test.go @@ -286,3 +286,53 @@ func TestNotZero(t *testing.T) { t.Error("Check should fail") } } + +func TestJSONEq(t *testing.T) { + mockT := new(MockT) + + JSONEq(mockT, `{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`) + if mockT.Failed { + t.Error("Check should pass") + } + + JSONEq(mockT, `{"hello": "world", "foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) + if mockT.Failed { + t.Error("Check should pass") + } + + JSONEq(mockT, "{\r\n\t\"numeric\": 1.5,\r\n\t\"array\": [{\"foo\": \"bar\"}, 1, \"string\", [\"nested\", \"array\", 5.5]],\r\n\t\"hash\": {\"nested\": \"hash\", \"nested_slice\": [\"this\", \"is\", \"nested\"]},\r\n\t\"string\": \"foo\"\r\n}", + "{\r\n\t\"numeric\": 1.5,\r\n\t\"hash\": {\"nested\": \"hash\", \"nested_slice\": [\"this\", \"is\", \"nested\"]},\r\n\t\"string\": \"foo\",\r\n\t\"array\": [{\"foo\": \"bar\"}, 1, \"string\", [\"nested\", \"array\", 5.5]]\r\n}") + if mockT.Failed { + t.Error("Check should pass") + } + + JSONEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `["foo", {"nested": "hash", "hello": "world"}]`) + if mockT.Failed { + t.Error("Check should pass") + } + + JSONEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`) + if !mockT.Failed { + t.Error("Check should fail") + } + + JSONEq(mockT, `{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`) + if !mockT.Failed { + t.Error("Check should fail") + } + + JSONEq(mockT, `{"foo": "bar"}`, "Not JSON") + if !mockT.Failed { + t.Error("Check should fail") + } + + JSONEq(mockT, "Not JSON", `{"foo": "bar", "hello": "world"}`) + if !mockT.Failed { + t.Error("Check should fail") + } + + JSONEq(mockT, "Not JSON", "Not JSON") + if !mockT.Failed { + t.Error("Check should fail") + } +}