Skip to content

Commit 07bb037

Browse files
committed
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
1 parent d250334 commit 07bb037

File tree

4 files changed

+119
-22
lines changed

4 files changed

+119
-22
lines changed

spring-test/src/main/java/org/springframework/test/util/JsonPathExpectationsHelper.java

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -171,7 +171,7 @@ public void assertValueIsNumber(String content) throws ParseException {
171171
public void assertValueIsArray(String content) throws ParseException {
172172
Object value = assertExistsAndReturn(content);
173173
String reason = "Expected an array at JSON path \"" + this.expression + "\" but found: " + value;
174-
assertTrue(reason, value instanceof List);
174+
assertThat(reason, value, instanceOf(List.class));
175175
}
176176

177177
/**
@@ -188,7 +188,10 @@ public void assertValueIsMap(String content) throws ParseException {
188188

189189
/**
190190
* Evaluate the JSON path expression against the supplied {@code content}
191-
* and assert that the resulting value exists.
191+
* and assert that a non-null value exists at the given path.
192+
* <p>If the JSON path expression is not
193+
* {@linkplain JsonPath#isDefinite() definite}, this method asserts
194+
* that the value at the given path is not <em>empty</em>.
192195
* @param content the JSON content
193196
*/
194197
public void exists(String content) throws ParseException {
@@ -197,8 +200,10 @@ public void exists(String content) throws ParseException {
197200

198201
/**
199202
* Evaluate the JSON path expression against the supplied {@code content}
200-
* and assert that the resulting value is empty (i.e., that a match for
201-
* the JSON path expression does not exist in the supplied content).
203+
* and assert that a value does not exist at the given path.
204+
* <p>If the JSON path expression is not
205+
* {@linkplain JsonPath#isDefinite() definite}, this method asserts
206+
* that the value at the given path is <em>empty</em>.
202207
* @param content the JSON content
203208
*/
204209
public void doesNotExist(String content) throws ParseException {
@@ -210,7 +215,7 @@ public void doesNotExist(String content) throws ParseException {
210215
return;
211216
}
212217
String reason = "Expected no value at JSON path \"" + this.expression + "\" but found: " + value;
213-
if (List.class.isInstance(value)) {
218+
if (pathIsIndefinite() && value instanceof List) {
214219
assertTrue(reason, ((List<?>) value).isEmpty());
215220
}
216221
else {
@@ -238,7 +243,14 @@ private Object assertExistsAndReturn(String content) throws ParseException {
238243
Object value = evaluateJsonPath(content);
239244
String reason = "No value at JSON path \"" + this.expression + "\"";
240245
assertTrue(reason, value != null);
246+
if (pathIsIndefinite() && value instanceof List) {
247+
assertTrue(reason, !((List<?>) value).isEmpty());
248+
}
241249
return value;
242250
}
243251

252+
private boolean pathIsIndefinite() {
253+
return !this.jsonPath.isDefinite();
254+
}
255+
244256
}

spring-test/src/main/java/org/springframework/test/web/servlet/result/JsonPathResultMatchers.java

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,8 +78,10 @@ public void match(MvcResult result) throws Exception {
7878

7979
/**
8080
* Evaluate the JSON path expression against the response content and
81-
* assert that the result is not empty (i.e., that a match for the JSON
82-
* path expression exists in the response content).
81+
* assert that a non-null value exists at the given path.
82+
* <p>If the JSON path expression is not
83+
* {@linkplain com.jayway.jsonpath.JsonPath#isDefinite definite},
84+
* this method asserts that the value at the given path is not <em>empty</em>.
8385
*/
8486
public ResultMatcher exists() {
8587
return new ResultMatcher() {
@@ -93,8 +95,10 @@ public void match(MvcResult result) throws Exception {
9395

9496
/**
9597
* Evaluate the JSON path expression against the response content and
96-
* assert that the result is empty (i.e., that a match for the JSON
97-
* path expression does not exist in the response content).
98+
* assert that a value does not exist at the given path.
99+
* <p>If the JSON path expression is not
100+
* {@linkplain com.jayway.jsonpath.JsonPath#isDefinite definite}, this
101+
* method asserts that the value at the given path is <em>empty</em>.
98102
*/
99103
public ResultMatcher doesNotExist() {
100104
return new ResultMatcher() {

spring-test/src/test/java/org/springframework/test/util/JsonPathExpectationsHelperTests.java

Lines changed: 74 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -31,16 +31,23 @@
3131
*/
3232
public class JsonPathExpectationsHelperTests {
3333

34-
private static final String CONTENT = "{" + //
35-
"\"str\": \"foo\"," + //
36-
"\"nr\": 5," + //
37-
"\"bool\": true," + //
38-
"\"arr\": [\"bar\"]," + //
39-
"\"emptyArray\": []," + //
40-
"\"colorMap\": {\"red\": \"rojo\"}," + //
41-
"\"emptyMap\": {}," + //
34+
private static final String CONTENT = "{" + //
35+
"\"str\": \"foo\", " + //
36+
"\"num\": 5, " + //
37+
"\"bool\": true, " + //
38+
"\"arr\": [\"bar\"], " + //
39+
"\"emptyArray\": [], " + //
40+
"\"colorMap\": {\"red\": \"rojo\"}, " + //
41+
"\"emptyMap\": {} " + //
4242
"}";
4343

44+
private static final String SIMPSONS = "{ \"familyMembers\": [ " + //
45+
"{\"name\": \"Homer\" }, " + //
46+
"{\"name\": \"Marge\" }, " + //
47+
"{\"name\": \"Bart\" }, " + //
48+
"{\"name\": \"Lisa\" }, " + //
49+
"{\"name\": \"Maggie\"} " + //
50+
" ] }";
4451

4552
@Rule
4653
public final ExpectedException exception = ExpectedException.none();
@@ -51,21 +58,75 @@ public void exists() throws Exception {
5158
new JsonPathExpectationsHelper("$.str").exists(CONTENT);
5259
}
5360

61+
@Test
62+
public void existsForAnEmptyArray() throws Exception {
63+
new JsonPathExpectationsHelper("$.emptyArray").exists(CONTENT);
64+
}
65+
66+
@Test
67+
public void existsForAnEmptyMap() throws Exception {
68+
new JsonPathExpectationsHelper("$.emptyMap").exists(CONTENT);
69+
}
70+
71+
@Test
72+
public void existsForIndefinatePathWithResults() throws Exception {
73+
new JsonPathExpectationsHelper("$.familyMembers[?(@.name == 'Bart')]").exists(SIMPSONS);
74+
}
75+
76+
@Test
77+
public void existsForIndefinatePathWithEmptyResults() throws Exception {
78+
String expression = "$.familyMembers[?(@.name == 'Dilbert')]";
79+
exception.expect(AssertionError.class);
80+
exception.expectMessage("No value at JSON path \"" + expression + "\"");
81+
new JsonPathExpectationsHelper(expression).exists(SIMPSONS);
82+
}
83+
5484
@Test
5585
public void doesNotExist() throws Exception {
5686
new JsonPathExpectationsHelper("$.bogus").doesNotExist(CONTENT);
5787
}
5888

89+
@Test
90+
public void doesNotExistForAnEmptyArray() throws Exception {
91+
String expression = "$.emptyArray";
92+
exception.expect(AssertionError.class);
93+
exception.expectMessage("Expected no value at JSON path \"" + expression + "\" but found: []");
94+
new JsonPathExpectationsHelper(expression).doesNotExist(CONTENT);
95+
}
96+
97+
@Test
98+
public void doesNotExistForAnEmptyMap() throws Exception {
99+
String expression = "$.emptyMap";
100+
exception.expect(AssertionError.class);
101+
exception.expectMessage("Expected no value at JSON path \"" + expression + "\" but found: {}");
102+
new JsonPathExpectationsHelper(expression).doesNotExist(CONTENT);
103+
}
104+
105+
@Test
106+
public void doesNotExistForIndefinatePathWithResults() throws Exception {
107+
String expression = "$.familyMembers[?(@.name == 'Bart')]";
108+
exception.expect(AssertionError.class);
109+
exception.expectMessage("Expected no value at JSON path \"" + expression
110+
+ "\" but found: [{\"name\":\"Bart\"}]");
111+
new JsonPathExpectationsHelper(expression).doesNotExist(SIMPSONS);
112+
}
113+
114+
@Test
115+
public void doesNotExistForIndefinatePathWithEmptyResults() throws Exception {
116+
String expression = "$.familyMembers[?(@.name == 'Dilbert')]";
117+
new JsonPathExpectationsHelper(expression).doesNotExist(SIMPSONS);
118+
}
119+
59120
@Test
60121
public void assertValue() throws Exception {
61-
new JsonPathExpectationsHelper("$.nr").assertValue(CONTENT, 5);
122+
new JsonPathExpectationsHelper("$.num").assertValue(CONTENT, 5);
62123
}
63124

64125
@Test
65126
public void assertValueWithDifferentExpectedType() throws Exception {
66127
exception.expect(AssertionError.class);
67-
exception.expectMessage(equalTo("At JSON path \"$.nr\", type of value expected:<java.lang.String> but was:<java.lang.Integer>"));
68-
new JsonPathExpectationsHelper("$.nr").assertValue(CONTENT, "5");
128+
exception.expectMessage(equalTo("At JSON path \"$.num\", type of value expected:<java.lang.String> but was:<java.lang.Integer>"));
129+
new JsonPathExpectationsHelper("$.num").assertValue(CONTENT, "5");
69130
}
70131

71132
@Test
@@ -81,7 +142,7 @@ public void assertValueIsStringForNonString() throws Exception {
81142

82143
@Test
83144
public void assertValueIsNumber() throws Exception {
84-
new JsonPathExpectationsHelper("$.nr").assertValueIsNumber(CONTENT);
145+
new JsonPathExpectationsHelper("$.num").assertValueIsNumber(CONTENT);
85146
}
86147

87148
@Test
@@ -98,7 +159,7 @@ public void assertValueIsBoolean() throws Exception {
98159
@Test
99160
public void assertValueIsBooleanForNonBoolean() throws Exception {
100161
exception.expect(AssertionError.class);
101-
new JsonPathExpectationsHelper("$.nr").assertValueIsBoolean(CONTENT);
162+
new JsonPathExpectationsHelper("$.num").assertValueIsBoolean(CONTENT);
102163
}
103164

104165
@Test

spring-test/src/test/java/org/springframework/test/web/servlet/result/JsonPathResultMatchersTests.java

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ public void exists() throws Exception {
7474
new JsonPathResultMatchers("$.foo").exists().match(stubMvcResult);
7575
}
7676

77+
@Test
78+
public void existsForAnEmptyArray() throws Exception {
79+
new JsonPathResultMatchers("$.emptyArray").exists().match(stubMvcResult);
80+
}
81+
82+
@Test
83+
public void existsForAnEmptyMap() throws Exception {
84+
new JsonPathResultMatchers("$.emptyMap").exists().match(stubMvcResult);
85+
}
86+
7787
@Test(expected = AssertionError.class)
7888
public void existsNoMatch() throws Exception {
7989
new JsonPathResultMatchers("$.bogus").exists().match(stubMvcResult);
@@ -89,6 +99,16 @@ public void doesNotExistNoMatch() throws Exception {
8999
new JsonPathResultMatchers("$.foo").doesNotExist().match(stubMvcResult);
90100
}
91101

102+
@Test(expected = AssertionError.class)
103+
public void doesNotExistForAnEmptyArray() throws Exception {
104+
new JsonPathResultMatchers("$.emptyArray").doesNotExist().match(stubMvcResult);
105+
}
106+
107+
@Test(expected = AssertionError.class)
108+
public void doesNotExistForAnEmptyMap() throws Exception {
109+
new JsonPathResultMatchers("$.emptyMap").doesNotExist().match(stubMvcResult);
110+
}
111+
92112
@Test
93113
public void isArray() throws Exception {
94114
new JsonPathResultMatchers("$.qux").isArray().match(stubMvcResult);

0 commit comments

Comments
 (0)