Differentiate b/t (in)definite results in JsonPath assertions

Prior to this commit, the exists() method in JsonPathExpectationsHelper
correctly asserted that the evaluated JsonPath expression resulted in a
value (i.e., that a non-null value exists); however, if the value was
an empty array, the exists() method always threw an AssertionError.

The existing behavior makes sense if the JsonPath expression is
'indefinite' -- for example, if the expression uses a filter to select
results based on a predicate for which there is no match in the JSON
document, but the existing behavior is illogical and therefore invalid
if the JsonPath expression is 'definite' (i.e., directly references an
array in the JSON document that exists but happens to be empty). For
example, prior to this commit, the following threw an AssertionError.

    new JsonPathExpectationsHelper("$.arr").exists("{ 'arr': [] }");

Similar arguments can be made for the doesNotExist() method.

After thorough analysis of the status quo, it has become apparent that
the existing specialized treatment of arrays is a result of the fact
that the JsonPath library always returns an empty list if the path is
an 'indefinite' path that does not evaluate to a specific result.
Consult the discussion on "What is Returned When?" in the JsonPath
documentation for details:

    https://github.com/jayway/JsonPath#what-is-returned-when

This commit addresses these issues by ensuring that empty arrays are
considered existent if the JsonPath expression is definite but
nonexistent if the expression is indefinite.

Issue: SPR-13351
This commit is contained in:
Sam Brannen 2015-08-14 20:38:25 +02:00
parent d2503340e7
commit 07bb0378b9
4 changed files with 119 additions and 22 deletions

View File

@ -171,7 +171,7 @@ public class JsonPathExpectationsHelper {
public void assertValueIsArray(String content) throws ParseException {
Object value = assertExistsAndReturn(content);
String reason = "Expected an array at JSON path \"" + this.expression + "\" but found: " + value;
assertTrue(reason, value instanceof List);
assertThat(reason, value, instanceOf(List.class));
}
/**
@ -188,7 +188,10 @@ public class JsonPathExpectationsHelper {
/**
* Evaluate the JSON path expression against the supplied {@code content}
* and assert that the resulting value exists.
* and assert that a non-null value exists at the given path.
* <p>If the JSON path expression is not
* {@linkplain JsonPath#isDefinite() definite}, this method asserts
* that the value at the given path is not <em>empty</em>.
* @param content the JSON content
*/
public void exists(String content) throws ParseException {
@ -197,8 +200,10 @@ public class JsonPathExpectationsHelper {
/**
* Evaluate the JSON path expression against the supplied {@code content}
* and assert that the resulting value is empty (i.e., that a match for
* the JSON path expression does not exist in the supplied content).
* and assert that a value does not exist at the given path.
* <p>If the JSON path expression is not
* {@linkplain JsonPath#isDefinite() definite}, this method asserts
* that the value at the given path is <em>empty</em>.
* @param content the JSON content
*/
public void doesNotExist(String content) throws ParseException {
@ -210,7 +215,7 @@ public class JsonPathExpectationsHelper {
return;
}
String reason = "Expected no value at JSON path \"" + this.expression + "\" but found: " + value;
if (List.class.isInstance(value)) {
if (pathIsIndefinite() && value instanceof List) {
assertTrue(reason, ((List<?>) value).isEmpty());
}
else {
@ -238,7 +243,14 @@ public class JsonPathExpectationsHelper {
Object value = evaluateJsonPath(content);
String reason = "No value at JSON path \"" + this.expression + "\"";
assertTrue(reason, value != null);
if (pathIsIndefinite() && value instanceof List) {
assertTrue(reason, !((List<?>) value).isEmpty());
}
return value;
}
private boolean pathIsIndefinite() {
return !this.jsonPath.isDefinite();
}
}

View File

@ -78,8 +78,10 @@ public class JsonPathResultMatchers {
/**
* Evaluate the JSON path expression against the response content and
* assert that the result is not empty (i.e., that a match for the JSON
* path expression exists in the response content).
* assert that a non-null value exists at the given path.
* <p>If the JSON path expression is not
* {@linkplain com.jayway.jsonpath.JsonPath#isDefinite definite},
* this method asserts that the value at the given path is not <em>empty</em>.
*/
public ResultMatcher exists() {
return new ResultMatcher() {
@ -93,8 +95,10 @@ public class JsonPathResultMatchers {
/**
* Evaluate the JSON path expression against the response content and
* assert that the result is empty (i.e., that a match for the JSON
* path expression does not exist in the response content).
* assert that a value does not exist at the given path.
* <p>If the JSON path expression is not
* {@linkplain com.jayway.jsonpath.JsonPath#isDefinite definite}, this
* method asserts that the value at the given path is <em>empty</em>.
*/
public ResultMatcher doesNotExist() {
return new ResultMatcher() {

View File

@ -31,16 +31,23 @@ import static org.hamcrest.CoreMatchers.*;
*/
public class JsonPathExpectationsHelperTests {
private static final String CONTENT = "{" + //
"\"str\": \"foo\"," + //
"\"nr\": 5," + //
"\"bool\": true," + //
"\"arr\": [\"bar\"]," + //
"\"emptyArray\": []," + //
"\"colorMap\": {\"red\": \"rojo\"}," + //
"\"emptyMap\": {}," + //
private static final String CONTENT = "{" + //
"\"str\": \"foo\", " + //
"\"num\": 5, " + //
"\"bool\": true, " + //
"\"arr\": [\"bar\"], " + //
"\"emptyArray\": [], " + //
"\"colorMap\": {\"red\": \"rojo\"}, " + //
"\"emptyMap\": {} " + //
"}";
private static final String SIMPSONS = "{ \"familyMembers\": [ " + //
"{\"name\": \"Homer\" }, " + //
"{\"name\": \"Marge\" }, " + //
"{\"name\": \"Bart\" }, " + //
"{\"name\": \"Lisa\" }, " + //
"{\"name\": \"Maggie\"} " + //
" ] }";
@Rule
public final ExpectedException exception = ExpectedException.none();
@ -51,21 +58,75 @@ public class JsonPathExpectationsHelperTests {
new JsonPathExpectationsHelper("$.str").exists(CONTENT);
}
@Test
public void existsForAnEmptyArray() throws Exception {
new JsonPathExpectationsHelper("$.emptyArray").exists(CONTENT);
}
@Test
public void existsForAnEmptyMap() throws Exception {
new JsonPathExpectationsHelper("$.emptyMap").exists(CONTENT);
}
@Test
public void existsForIndefinatePathWithResults() throws Exception {
new JsonPathExpectationsHelper("$.familyMembers[?(@.name == 'Bart')]").exists(SIMPSONS);
}
@Test
public void existsForIndefinatePathWithEmptyResults() throws Exception {
String expression = "$.familyMembers[?(@.name == 'Dilbert')]";
exception.expect(AssertionError.class);
exception.expectMessage("No value at JSON path \"" + expression + "\"");
new JsonPathExpectationsHelper(expression).exists(SIMPSONS);
}
@Test
public void doesNotExist() throws Exception {
new JsonPathExpectationsHelper("$.bogus").doesNotExist(CONTENT);
}
@Test
public void doesNotExistForAnEmptyArray() throws Exception {
String expression = "$.emptyArray";
exception.expect(AssertionError.class);
exception.expectMessage("Expected no value at JSON path \"" + expression + "\" but found: []");
new JsonPathExpectationsHelper(expression).doesNotExist(CONTENT);
}
@Test
public void doesNotExistForAnEmptyMap() throws Exception {
String expression = "$.emptyMap";
exception.expect(AssertionError.class);
exception.expectMessage("Expected no value at JSON path \"" + expression + "\" but found: {}");
new JsonPathExpectationsHelper(expression).doesNotExist(CONTENT);
}
@Test
public void doesNotExistForIndefinatePathWithResults() throws Exception {
String expression = "$.familyMembers[?(@.name == 'Bart')]";
exception.expect(AssertionError.class);
exception.expectMessage("Expected no value at JSON path \"" + expression
+ "\" but found: [{\"name\":\"Bart\"}]");
new JsonPathExpectationsHelper(expression).doesNotExist(SIMPSONS);
}
@Test
public void doesNotExistForIndefinatePathWithEmptyResults() throws Exception {
String expression = "$.familyMembers[?(@.name == 'Dilbert')]";
new JsonPathExpectationsHelper(expression).doesNotExist(SIMPSONS);
}
@Test
public void assertValue() throws Exception {
new JsonPathExpectationsHelper("$.nr").assertValue(CONTENT, 5);
new JsonPathExpectationsHelper("$.num").assertValue(CONTENT, 5);
}
@Test
public void assertValueWithDifferentExpectedType() throws Exception {
exception.expect(AssertionError.class);
exception.expectMessage(equalTo("At JSON path \"$.nr\", type of value expected:<java.lang.String> but was:<java.lang.Integer>"));
new JsonPathExpectationsHelper("$.nr").assertValue(CONTENT, "5");
exception.expectMessage(equalTo("At JSON path \"$.num\", type of value expected:<java.lang.String> but was:<java.lang.Integer>"));
new JsonPathExpectationsHelper("$.num").assertValue(CONTENT, "5");
}
@Test
@ -81,7 +142,7 @@ public class JsonPathExpectationsHelperTests {
@Test
public void assertValueIsNumber() throws Exception {
new JsonPathExpectationsHelper("$.nr").assertValueIsNumber(CONTENT);
new JsonPathExpectationsHelper("$.num").assertValueIsNumber(CONTENT);
}
@Test
@ -98,7 +159,7 @@ public class JsonPathExpectationsHelperTests {
@Test
public void assertValueIsBooleanForNonBoolean() throws Exception {
exception.expect(AssertionError.class);
new JsonPathExpectationsHelper("$.nr").assertValueIsBoolean(CONTENT);
new JsonPathExpectationsHelper("$.num").assertValueIsBoolean(CONTENT);
}
@Test

View File

@ -74,6 +74,16 @@ public class JsonPathResultMatchersTests {
new JsonPathResultMatchers("$.foo").exists().match(stubMvcResult);
}
@Test
public void existsForAnEmptyArray() throws Exception {
new JsonPathResultMatchers("$.emptyArray").exists().match(stubMvcResult);
}
@Test
public void existsForAnEmptyMap() throws Exception {
new JsonPathResultMatchers("$.emptyMap").exists().match(stubMvcResult);
}
@Test(expected = AssertionError.class)
public void existsNoMatch() throws Exception {
new JsonPathResultMatchers("$.bogus").exists().match(stubMvcResult);
@ -89,6 +99,16 @@ public class JsonPathResultMatchersTests {
new JsonPathResultMatchers("$.foo").doesNotExist().match(stubMvcResult);
}
@Test(expected = AssertionError.class)
public void doesNotExistForAnEmptyArray() throws Exception {
new JsonPathResultMatchers("$.emptyArray").doesNotExist().match(stubMvcResult);
}
@Test(expected = AssertionError.class)
public void doesNotExistForAnEmptyMap() throws Exception {
new JsonPathResultMatchers("$.emptyMap").doesNotExist().match(stubMvcResult);
}
@Test
public void isArray() throws Exception {
new JsonPathResultMatchers("$.qux").isArray().match(stubMvcResult);