Skip to content

Commit d09b0fe

Browse files
rstoyanchevjhoeller
authored andcommitted
Support target type in JsonPath assertions
This change adds support for a target type in JsonPath assertions in Spring MVC Test. The existing assertValue(String expression, Object expectedValue) transparently falls back on using an alternative JsonPath API that allows specifying the target type to coerce to. There is also a new overloaded method assertValue(String expression, Matcher<T> matcher, Class<T> targetType) for use with Hamcrest matchers where the target type can be specified. Issue: SPR-14498 (cherry picked from commit 7fdb892)
1 parent 798d866 commit d09b0fe

File tree

3 files changed

+69
-31
lines changed

3 files changed

+69
-31
lines changed

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

+42-24
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2015 the original author or authors.
2+
* Copyright 2002-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -16,11 +16,9 @@
1616

1717
package org.springframework.test.util;
1818

19-
import java.text.ParseException;
2019
import java.util.List;
2120
import java.util.Map;
2221

23-
import com.jayway.jsonpath.InvalidPathException;
2422
import com.jayway.jsonpath.JsonPath;
2523
import org.hamcrest.Matcher;
2624

@@ -71,18 +69,33 @@ public JsonPathExpectationsHelper(String expression, Object... args) {
7169
* @param matcher the matcher with which to assert the result
7270
*/
7371
@SuppressWarnings("unchecked")
74-
public <T> void assertValue(String content, Matcher<T> matcher) throws ParseException {
72+
public <T> void assertValue(String content, Matcher<T> matcher) {
7573
T value = (T) evaluateJsonPath(content);
7674
assertThat("JSON path \"" + this.expression + "\"", value, matcher);
7775
}
7876

77+
/**
78+
* An overloaded variant of {@link #assertValue(String, Matcher)} that also
79+
* accepts a target type for the resulting value. This can be useful for
80+
* matching numbers reliably for example coercing an integer into a double.
81+
* @param content the JSON content
82+
* @param matcher the matcher with which to assert the result
83+
* @param targetType a the expected type of the resulting value
84+
* @since 4.3.3
85+
*/
86+
@SuppressWarnings("unchecked")
87+
public <T> void assertValue(String content, Matcher<T> matcher, Class<T> targetType) {
88+
T value = (T) evaluateJsonPath(content, targetType);
89+
assertThat("JSON path \"" + this.expression + "\"", value, matcher);
90+
}
91+
7992
/**
8093
* Evaluate the JSON path expression against the supplied {@code content}
8194
* and assert that the result is equal to the expected value.
8295
* @param content the JSON content
8396
* @param expectedValue the expected value
8497
*/
85-
public void assertValue(String content, Object expectedValue) throws ParseException {
98+
public void assertValue(String content, Object expectedValue) {
8699
Object actualValue = evaluateJsonPath(content);
87100
if ((actualValue instanceof List) && !(expectedValue instanceof List)) {
88101
@SuppressWarnings("rawtypes")
@@ -96,8 +109,9 @@ public void assertValue(String content, Object expectedValue) throws ParseExcept
96109
actualValue = actualValueList.get(0);
97110
}
98111
else if (actualValue != null && expectedValue != null) {
99-
assertEquals("At JSON path \"" + this.expression + "\", type of value",
100-
expectedValue.getClass().getName(), actualValue.getClass().getName());
112+
if (!actualValue.getClass().equals(expectedValue.getClass())) {
113+
actualValue = evaluateJsonPath(content, expectedValue.getClass());
114+
}
101115
}
102116
assertEquals("JSON path \"" + this.expression + "\"", expectedValue, actualValue);
103117
}
@@ -108,7 +122,7 @@ else if (actualValue != null && expectedValue != null) {
108122
* @param content the JSON content
109123
* @since 4.2.1
110124
*/
111-
public void assertValueIsString(String content) throws ParseException {
125+
public void assertValueIsString(String content) {
112126
Object value = assertExistsAndReturn(content);
113127
assertThat(failureReason("a string", value), value, instanceOf(String.class));
114128
}
@@ -119,7 +133,7 @@ public void assertValueIsString(String content) throws ParseException {
119133
* @param content the JSON content
120134
* @since 4.2.1
121135
*/
122-
public void assertValueIsBoolean(String content) throws ParseException {
136+
public void assertValueIsBoolean(String content) {
123137
Object value = assertExistsAndReturn(content);
124138
assertThat(failureReason("a boolean", value), value, instanceOf(Boolean.class));
125139
}
@@ -130,7 +144,7 @@ public void assertValueIsBoolean(String content) throws ParseException {
130144
* @param content the JSON content
131145
* @since 4.2.1
132146
*/
133-
public void assertValueIsNumber(String content) throws ParseException {
147+
public void assertValueIsNumber(String content) {
134148
Object value = assertExistsAndReturn(content);
135149
assertThat(failureReason("a number", value), value, instanceOf(Number.class));
136150
}
@@ -140,7 +154,7 @@ public void assertValueIsNumber(String content) throws ParseException {
140154
* and assert that the resulting value is an array.
141155
* @param content the JSON content
142156
*/
143-
public void assertValueIsArray(String content) throws ParseException {
157+
public void assertValueIsArray(String content) {
144158
Object value = assertExistsAndReturn(content);
145159
assertThat(failureReason("an array", value), value, instanceOf(List.class));
146160
}
@@ -151,7 +165,7 @@ public void assertValueIsArray(String content) throws ParseException {
151165
* @param content the JSON content
152166
* @since 4.2.1
153167
*/
154-
public void assertValueIsMap(String content) throws ParseException {
168+
public void assertValueIsMap(String content) {
155169
Object value = assertExistsAndReturn(content);
156170
assertThat(failureReason("a map", value), value, instanceOf(Map.class));
157171
}
@@ -164,7 +178,7 @@ public void assertValueIsMap(String content) throws ParseException {
164178
* that the value at the given path is not <em>empty</em>.
165179
* @param content the JSON content
166180
*/
167-
public void exists(String content) throws ParseException {
181+
public void exists(String content) {
168182
assertExistsAndReturn(content);
169183
}
170184

@@ -176,7 +190,7 @@ public void exists(String content) throws ParseException {
176190
* that the value at the given path is <em>empty</em>.
177191
* @param content the JSON content
178192
*/
179-
public void doesNotExist(String content) throws ParseException {
193+
public void doesNotExist(String content) {
180194
Object value;
181195
try {
182196
value = evaluateJsonPath(content);
@@ -189,7 +203,7 @@ public void doesNotExist(String content) throws ParseException {
189203
assertTrue(reason, ((List<?>) value).isEmpty());
190204
}
191205
else {
192-
assertTrue(reason, value == null);
206+
assertTrue(reason, (value == null));
193207
}
194208
}
195209

@@ -200,7 +214,7 @@ public void doesNotExist(String content) throws ParseException {
200214
* {@link ObjectUtils#isEmpty(Object)}.
201215
* @param content the JSON content
202216
*/
203-
public void assertValueIsEmpty(String content) throws ParseException {
217+
public void assertValueIsEmpty(String content) {
204218
Object value = evaluateJsonPath(content);
205219
assertTrue(failureReason("an empty value", value), ObjectUtils.isEmpty(value));
206220
}
@@ -212,33 +226,37 @@ public void assertValueIsEmpty(String content) throws ParseException {
212226
* {@link ObjectUtils#isEmpty(Object)}.
213227
* @param content the JSON content
214228
*/
215-
public void assertValueIsNotEmpty(String content) throws ParseException {
229+
public void assertValueIsNotEmpty(String content) {
216230
Object value = evaluateJsonPath(content);
217231
assertTrue(failureReason("a non-empty value", value), !ObjectUtils.isEmpty(value));
218232
}
219233

220234
private String failureReason(String expectedDescription, Object value) {
221235
return String.format("Expected %s at JSON path \"%s\" but found: %s", expectedDescription, this.expression,
222-
ObjectUtils.nullSafeToString(StringUtils.quoteIfString(value)));
236+
ObjectUtils.nullSafeToString(StringUtils.quoteIfString(value)));
223237
}
224238

225-
private Object evaluateJsonPath(String content) throws ParseException {
239+
private Object evaluateJsonPath(String content) {
226240
String message = "No value at JSON path \"" + this.expression + "\", exception: ";
227241
try {
228242
return this.jsonPath.read(content);
229243
}
230-
catch (InvalidPathException ex) {
244+
catch (Throwable ex) {
231245
throw new AssertionError(message + ex.getMessage());
232246
}
233-
catch (ArrayIndexOutOfBoundsException ex) {
234-
throw new AssertionError(message + ex.getMessage());
247+
}
248+
249+
private Object evaluateJsonPath(String content, Class<?> targetType) {
250+
String message = "No value at JSON path \"" + this.expression + "\", exception: ";
251+
try {
252+
return JsonPath.parse(content).read(this.expression, targetType);
235253
}
236-
catch (IndexOutOfBoundsException ex) {
254+
catch (Throwable ex) {
237255
throw new AssertionError(message + ex.getMessage());
238256
}
239257
}
240258

241-
private Object assertExistsAndReturn(String content) throws ParseException {
259+
private Object assertExistsAndReturn(String content) {
242260
Object value = evaluateJsonPath(content);
243261
String reason = "No value at JSON path \"" + this.expression + "\"";
244262
assertTrue(reason, value != null);

spring-test/src/main/java/org/springframework/test/web/client/match/JsonPathRequestMatchers.java

+17
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,23 @@ protected void matchInternal(MockClientHttpRequest request) throws IOException,
6969
};
7070
}
7171

72+
/**
73+
* An overloaded variant of (@link {@link #value(Matcher)} that also
74+
* accepts a target type for the resulting value that the matcher can work
75+
* reliably against. This can be useful for matching numbers reliably for
76+
* example coercing an integer into a double.
77+
* @since 4.3.3
78+
*/
79+
public <T> RequestMatcher value(final Matcher<T> matcher, final Class<T> targetType) {
80+
return new AbstractJsonPathRequestMatcher() {
81+
@Override
82+
protected void matchInternal(MockClientHttpRequest request) throws IOException, ParseException {
83+
String body = request.getBodyAsString();
84+
JsonPathRequestMatchers.this.jsonPathHelper.assertValue(body, matcher, targetType);
85+
}
86+
};
87+
}
88+
7289
/**
7390
* Evaluate the JSON path expression against the request content and
7491
* assert that the result is equal to the supplied value.

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

+10-7
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2004-2015 the original author or authors.
2+
* Copyright 2004-2016 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -20,7 +20,7 @@
2020
import org.junit.Test;
2121
import org.junit.rules.ExpectedException;
2222

23-
import static org.hamcrest.CoreMatchers.*;
23+
import static org.hamcrest.core.Is.*;
2424

2525
/**
2626
* Unit tests for {@link JsonPathExpectationsHelper}.
@@ -222,11 +222,14 @@ public void assertValue() throws Exception {
222222
new JsonPathExpectationsHelper("$.num").assertValue(CONTENT, 5);
223223
}
224224

225-
@Test
226-
public void assertValueWithDifferentExpectedType() throws Exception {
227-
exception.expect(AssertionError.class);
228-
exception.expectMessage(equalTo("At JSON path \"$.num\", type of value expected:<java.lang.String> but was:<java.lang.Integer>"));
229-
new JsonPathExpectationsHelper("$.num").assertValue(CONTENT, "5");
225+
@Test // SPR-14498
226+
public void assertValueWithNumberConversion() throws Exception {
227+
new JsonPathExpectationsHelper("$.num").assertValue(CONTENT, 5.0);
228+
}
229+
230+
@Test // SPR-14498
231+
public void assertValueWithNumberConversionAndMatcher() throws Exception {
232+
new JsonPathExpectationsHelper("$.num").assertValue(CONTENT, is(5.0), Double.class);
230233
}
231234

232235
@Test

0 commit comments

Comments
 (0)