Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Json matching function #1690

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions assert/assertion_format.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

49 changes: 49 additions & 0 deletions assert/assertion_forward.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

47 changes: 47 additions & 0 deletions assert/assertions.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ import (
"github.com/davecgh/go-spew/spew"
"github.com/pmezard/go-difflib/difflib"

"github.com/stretchr/testify/assert/jsonmatch"
// Wrapper around gopkg.in/yaml.v3
"github.com/stretchr/testify/assert/yaml"
)
Expand Down Expand Up @@ -1821,6 +1822,52 @@ func JSONEq(t TestingT, expected string, actual string, msgAndArgs ...interface{
return Equal(t, expectedJSONAsInterface, actualJSONAsInterface, msgAndArgs...)
}

type ValueMatchers = jsonmatch.Matchers

// JSONMatchesBy asserts that two JSON strings are equivalent using one or more custom JSON matchers.
// the value passed into the matcher will be on of the following types:
// - string
// - float64
// - bool
// - nil
// - []interface{}
// - map[string]interface{}
//
// Can use nil for matchers to test equality of pure json.
//
// Note: in cases where expected and actual are not equal, the value for the matcher will be displayed as unequal,
// regardless of the output of the matcher function. This should be fixed in a future release.
//
// assert.JSONMatchesBy(t, `{ "foo": "$NOT_EMPTY" }`, `{ "foo": "baz" }`, assert.ValueMatchers{
// "$NOT_EMPTY": func(v interface) bool { return v != "" },
// })
func JSONMatchesBy(t TestingT, expected string, actual string, matchers ValueMatchers, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
h.Helper()
}

var expectedJSONAsInterface, actualJSONAsInterface interface{}

if err := json.Unmarshal([]byte(expected), &expectedJSONAsInterface); err != nil {
return Fail(t, fmt.Sprintf("Expected value ('%s') is not valid json.\nJSON parsing error: '%s'", expected, err.Error()), msgAndArgs...)
}

if err := json.Unmarshal([]byte(actual), &actualJSONAsInterface); err != nil {
return Fail(t, fmt.Sprintf("Input ('%s') needs to be valid json.\nJSON parsing error: '%s'", actual, err.Error()), msgAndArgs...)
}

if !jsonmatch.MatchesBy(expectedJSONAsInterface, actualJSONAsInterface, matchers) {
diff := diff(expectedJSONAsInterface, actualJSONAsInterface)
expected, actual = formatUnequalValues(expectedJSONAsInterface, actualJSONAsInterface)
return Fail(t, fmt.Sprintf("Not equal: \n"+
"expected: %s\n"+
"actual : %s%s", expectedJSONAsInterface, actualJSONAsInterface, diff), msgAndArgs...)

}

return true
}

// YAMLEq asserts that two YAML strings are equivalent.
func YAMLEq(t TestingT, expected string, actual string, msgAndArgs ...interface{}) bool {
if h, ok := t.(tHelper); ok {
Expand Down
147 changes: 147 additions & 0 deletions assert/assertions_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2375,6 +2375,153 @@ func TestJSONEq_ArraysOfDifferentOrder(t *testing.T) {
False(t, JSONEq(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `[{ "hello": "world", "nested": "hash"}, "foo"]`))
}

var notEmpty = ValueMatchers{
"$NOT_EMPTY": func(v interface{}) bool {
switch v := v.(type) {
case string:
return v != ""
default:
return false
}
},
}

func TestJSONMatchesBy_EqualSONString_NoMatchers(t *testing.T) {
mockT := new(testing.T)
True(t, JSONMatchesBy(
mockT,
`{"hello": "world", "foo": "bar"}`,
`{"hello": "world", "foo": "bar"}`,
nil,
))
}

func TestJSONMatchesBy_EqualSONString_WithMatchers(t *testing.T) {
mockT := new(testing.T)
True(t, JSONMatchesBy(
mockT,
`{"hello": "world", "foo": "$NOT_EMPTY"}`,
`{"hello": "world", "foo": "bar"}`,
notEmpty,
))
}

func TestJSONMatchesBy_EqualSONString_WithNotMatchingMatchers(t *testing.T) {
mockT := new(testing.T)
False(t, JSONMatchesBy(
mockT,
`{"hello": "world", "foo": "$NOT_EMPTY"}`,
`{"hello": "world", "foo": ""}`,
notEmpty,
))
}

func TestJSONMatchesBy_EquivalentButNotEqual_NoMatchers(t *testing.T) {
mockT := new(testing.T)
True(t, JSONMatchesBy(
mockT,
`{"hello": "world", "foo": "bar"}`,
`{"foo": "bar", "hello": "world"}`,
nil,
))
}

func TestJSONMatchesBy_EquivalentButNotEqual_WithMatchers(t *testing.T) {
mockT := new(testing.T)
True(t, JSONMatchesBy(
mockT,
`{"hello": "$NOT_EMPTY", "foo": "bar"}`,
`{"foo": "bar", "hello": "world"}`,
notEmpty,
))
}

func TestJSONMatchesBy_MatchersOnDifferentTypes(t *testing.T) {
mockT := new(testing.T)
True(t, JSONMatchesBy(
mockT,
`{"hello": "$SOME_NUMBER", "foo": "bar"}`,
`{"foo": "bar", "hello": 5}`,
ValueMatchers{
"$SOME_NUMBER": func(v interface{}) bool {
_, ok := v.(float64)
return ok
},
},
))
}

func TestJSONMatchesBy_HashOfArraysAndHashes(t *testing.T) {
mockT := new(testing.T)
True(t, JSONMatchesBy(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}", nil))
}

func TestJSONMatchesBy_Array_NoMatchers(t *testing.T) {
mockT := new(testing.T)
True(t, JSONMatchesBy(
mockT, `["foo", {"hello": "world", "nested": "hash"}]`,
`["foo", {"nested": "hash", "hello": "world"}]`,
nil,
))
}

func TestJSONMatchesBy_Array_WithMatchers(t *testing.T) {
mockT := new(testing.T)
True(t, JSONMatchesBy(
mockT, `["$NOT_EMPTY", {"hello": "world", "nested": "hash"}]`,
`["foo", {"nested": "hash", "hello": "world"}]`,
notEmpty,
))
}

func TestJSONMatchesBy_HashAndArrayNotEquivalent_NoMatchers(t *testing.T) {
mockT := new(testing.T)
False(t, JSONMatchesBy(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `{"foo": "bar", {"nested": "hash", "hello": "world"}}`, nil))
}

func TestJSONMatchesBy_HashesNotEquivalent_NoMatchers(t *testing.T) {
mockT := new(testing.T)
False(t, JSONMatchesBy(mockT, `{"foo": "bar"}`, `{"foo": "bar", "hello": "world"}`, nil))
}

func TestJSONMatchesBy_ActualIsNotJSON(t *testing.T) {
mockT := new(testing.T)
False(t, JSONMatchesBy(mockT, `{"foo": "bar"}`, "Not JSON", nil))
}

func TestJSONMatchesBy_ExpectedIsNotJSON(t *testing.T) {
mockT := new(testing.T)
False(t, JSONMatchesBy(mockT, "Not JSON", `{"foo": "bar", "hello": "world"}`, nil))
}

func TestJSONMatchesBy_ArraysOfDifferentOrder(t *testing.T) {
mockT := new(testing.T)
False(t, JSONMatchesBy(mockT, `["foo", {"hello": "world", "nested": "hash"}]`, `[{ "hello": "world", "nested": "hash"}, "foo"]`, nil))
}

func TestJSONMatchesBy_DeeplyNestedMatchers(t *testing.T) {
mockT := new(testing.T)
True(t, JSONMatchesBy(
mockT,
`["$THREE", {"foo": {"bar": {"baz": "$NOT_EMPTY"}}}, {"nested": { "array": ["$NOT_EMPTY", "foo"]}}]`,
`[3, {"foo": {"bar": {"baz": "qux"}}}, {"nested": { "array": ["quux", "foo"]}}]`,
ValueMatchers{
"$THREE": func(v interface{}) bool { return v == 3.0 },
"$NOT_EMPTY": func(v interface{}) bool { return v != "" },
}))
}

func TestJSONMatchesBy_EmptyObject(t *testing.T) {
mockT := new(testing.T)
True(t, JSONMatchesBy(mockT, "{}", "{}", nil))
}

func TestJSONMatchesBy_EmptyArray(t *testing.T) {
mockT := new(testing.T)
True(t, JSONMatchesBy(mockT, "[]", "[]", nil))
}

func TestYAMLEq_EqualYAMLString(t *testing.T) {
mockT := new(testing.T)
True(t, YAMLEq(mockT, `{"hello": "world", "foo": "bar"}`, `{"hello": "world", "foo": "bar"}`))
Expand Down
Loading
Loading