Introduce additional JsonPath matchers in Spring MVC Test

This commit introduces the following methods in JsonPathResultMatchers
in the Spring MVC Test framework.

- isString()
- isBoolean()
- isNumber()
- isMap()

In addition, this commit overhauls the Javadoc in
JsonPathResultMatchers and JsonPathExpectationsHelper.

Issue: SPR-13320
This commit is contained in:
Craig Andrews
2015-08-06 00:47:33 -04:00
committed by Sam Brannen
parent 48b965ad33
commit fffdd1e9e9
5 changed files with 293 additions and 104 deletions

View File

@@ -20,16 +20,21 @@ import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.text.ParseException;
import java.util.List;
import java.util.Map;
import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.JsonPath;
import org.hamcrest.Matcher;
import org.springframework.util.Assert;
import org.springframework.util.ReflectionUtils;
import static org.hamcrest.MatcherAssert.*;
import static org.springframework.test.util.AssertionErrors.*;
import com.jayway.jsonpath.InvalidPathException;
import com.jayway.jsonpath.JsonPath;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.springframework.test.util.AssertionErrors.assertEquals;
import static org.springframework.test.util.AssertionErrors.assertTrue;
import static org.springframework.test.util.AssertionErrors.fail;
/**
* A helper class for applying assertions via JSON path expressions.
@@ -39,6 +44,8 @@ import static org.springframework.test.util.AssertionErrors.*;
*
* @author Rossen Stoyanchev
* @author Juergen Hoeller
* @author Craig Andrews
* @author Sam Brannen
* @since 3.2
*/
public class JsonPathExpectationsHelper {
@@ -69,12 +76,13 @@ public class JsonPathExpectationsHelper {
/**
* Construct a new JsonPathExpectationsHelper.
* @param expression the JsonPath expression
* @param args arguments to parameterize the JSON path expression with
* Construct a new {@code JsonPathExpectationsHelper}.
* @param expression the {@link JsonPath} expression; never {@code null} or empty
* @param args arguments to parameterize the {@code JsonPath} expression, with
* formatting specifiers defined in {@link String#format(String, Object...)}
*/
public JsonPathExpectationsHelper(String expression, Object... args) {
Assert.hasText(expression, "expression must not be null or empty");
this.expression = String.format(expression, args);
this.jsonPath = (JsonPath) ReflectionUtils.invokeMethod(
compileMethod, null, this.expression, emptyFilters);
@@ -82,37 +90,25 @@ public class JsonPathExpectationsHelper {
/**
* Evaluate the JSON path and assert the resulting value with the given {@code Matcher}.
* @param content the response content
* @param matcher the matcher to assert on the resulting json path
* Evaluate the JSON path expression against the supplied {@code content}
* and assert the resulting value with the given {@code Matcher}.
* @param content the JSON response content
* @param matcher the matcher with which to assert the result
*/
@SuppressWarnings("unchecked")
public <T> void assertValue(String content, Matcher<T> matcher) throws ParseException {
T value = (T) evaluateJsonPath(content);
assertThat("JSON path " + this.expression, value, matcher);
}
private Object evaluateJsonPath(String content) throws ParseException {
String message = "No value for JSON path: " + this.expression + ", exception: ";
try {
return this.jsonPath.read(content);
}
catch (InvalidPathException ex) {
throw new AssertionError(message + ex.getMessage());
}
catch (ArrayIndexOutOfBoundsException ex) {
throw new AssertionError(message + ex.getMessage());
}
catch (IndexOutOfBoundsException ex) {
throw new AssertionError(message + ex.getMessage());
}
assertThat("JSON path \"" + this.expression + "\"", value, matcher);
}
/**
* Apply the JSON path and assert the resulting value.
* Evaluate the JSON path expression against the supplied {@code content}
* and assert that the result is equal to the expected value.
* @param content the JSON response content
* @param expectedValue the expected value
*/
public void assertValue(String responseContent, Object expectedValue) throws ParseException {
Object actualValue = evaluateJsonPath(responseContent);
public void assertValue(String content, Object expectedValue) throws ParseException {
Object actualValue = evaluateJsonPath(content);
if ((actualValue instanceof List) && !(expectedValue instanceof List)) {
@SuppressWarnings("rawtypes")
List actualValueList = (List) actualValue;
@@ -120,41 +116,90 @@ public class JsonPathExpectationsHelper {
fail("No matching value for JSON path \"" + this.expression + "\"");
}
if (actualValueList.size() != 1) {
fail("Got a list of values " + actualValue + " instead of the value " + expectedValue);
fail("Got a list of values " + actualValue + " instead of the expected single value " + expectedValue);
}
actualValue = actualValueList.get(0);
}
else if (actualValue != null && expectedValue != null) {
assertEquals("For JSON path " + this.expression + " type of value",
expectedValue.getClass(), actualValue.getClass());
assertEquals("For JSON path \"" + this.expression + "\", type of value",
expectedValue.getClass().getName(), actualValue.getClass().getName());
}
assertEquals("JSON path " + this.expression, expectedValue, actualValue);
assertEquals("JSON path \"" + this.expression + "\"", expectedValue, actualValue);
}
/**
* Apply the JSON path and assert the resulting value is an array.
* Evaluate the JSON path expression against the supplied {@code content}
* and assert that the resulting value is a {@link String}.
* @param content the JSON response content
* @since 4.2.1
*/
public void assertValueIsArray(String responseContent) throws ParseException {
Object actualValue = evaluateJsonPath(responseContent);
assertTrue("No value for JSON path \"" + this.expression + "\"", actualValue != null);
String reason = "Expected array at JSON path " + this.expression + " but found " + actualValue;
assertTrue(reason, actualValue instanceof List);
public void assertValueIsString(String content) throws ParseException {
Object value = assertExistsAndReturn(content);
String reason = "Expected string at JSON path " + this.expression + " but found " + value;
assertThat(reason, value, instanceOf(String.class));
}
/**
* Evaluate the JSON path and assert the resulting content exists.
* Evaluate the JSON path expression against the supplied {@code content}
* and assert that the resulting value is a {@link Boolean}.
* @param content the JSON response content
* @since 4.2.1
*/
public void assertValueIsBoolean(String content) throws ParseException {
Object value = assertExistsAndReturn(content);
String reason = "Expected boolean at JSON path " + this.expression + " but found " + value;
assertThat(reason, value, instanceOf(Boolean.class));
}
/**
* Evaluate the JSON path expression against the supplied {@code content}
* and assert that the resulting value is a {@link Number}.
* @param content the JSON response content
* @since 4.2.1
*/
public void assertValueIsNumber(String content) throws ParseException {
Object value = assertExistsAndReturn(content);
String reason = "Expected number at JSON path " + this.expression + " but found " + value;
assertThat(reason, value, instanceOf(Number.class));
}
/**
* Evaluate the JSON path expression against the supplied {@code content}
* and assert that the resulting value is an array.
* @param content the JSON response content
*/
public void assertValueIsArray(String content) throws ParseException {
Object value = assertExistsAndReturn(content);
String reason = "Expected array for JSON path \"" + this.expression + "\" but found " + value;
assertTrue(reason, value instanceof List);
}
/**
* Evaluate the JSON path expression against the supplied {@code content}
* and assert that the resulting value is a {@link Map}.
* @param content the JSON response content
* @since 4.2.1
*/
public void assertValueIsMap(String content) throws ParseException {
Object value = assertExistsAndReturn(content);
String reason = "Expected map at JSON path " + this.expression + " but found " + value;
assertThat(reason, value, instanceOf(Map.class));
}
/**
* Evaluate the JSON path expression against the supplied {@code content}
* and assert that the resulting value exists.
* @param content the JSON response content
*/
public void exists(String content) throws ParseException {
Object value = evaluateJsonPath(content);
String reason = "No value for JSON path " + this.expression;
assertTrue(reason, value != null);
if (List.class.isInstance(value)) {
assertTrue(reason, !((List<?>) value).isEmpty());
}
assertExistsAndReturn(content);
}
/**
* Evaluate the JSON path and assert it doesn't point to any content.
* 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).
* @param content the JSON response content
*/
public void doesNotExist(String content) throws ParseException {
Object value;
@@ -173,4 +218,30 @@ public class JsonPathExpectationsHelper {
}
}
private Object evaluateJsonPath(String content) throws ParseException {
String message = "No value for JSON path \"" + this.expression + "\", exception: ";
try {
return this.jsonPath.read(content);
}
catch (InvalidPathException ex) {
throw new AssertionError(message + ex.getMessage());
}
catch (ArrayIndexOutOfBoundsException ex) {
throw new AssertionError(message + ex.getMessage());
}
catch (IndexOutOfBoundsException ex) {
throw new AssertionError(message + ex.getMessage());
}
}
private Object assertExistsAndReturn(String content) throws ParseException {
Object value = evaluateJsonPath(content);
String reason = "No value for JSON path \"" + this.expression + "\"";
assertTrue(reason, value != null);
if (List.class.isInstance(value)) {
assertTrue(reason, !((List<?>) value).isEmpty());
}
return value;
}
}

View File

@@ -24,11 +24,13 @@ import org.springframework.test.web.servlet.ResultMatcher;
/**
* Factory for assertions on the response content using
* <a href="http://goessner.net/articles/JsonPath/">JSONPath</a> expressions.
* <a href="https://github.com/jayway/JsonPath">JsonPath</a> expressions.
* <p>An instance of this class is typically accessed via
* {@link MockMvcResultMatchers#jsonPath}.
*
* @author Rossen Stoyanchev
* @author Craig Andrews
* @author Sam Brannen
* @since 3.2
*/
public class JsonPathResultMatchers {
@@ -37,8 +39,8 @@ public class JsonPathResultMatchers {
/**
* Protected constructor. Use
* {@link MockMvcResultMatchers#jsonPath(String, Object...)} or
* Protected constructor.
* <p>Use {@link MockMvcResultMatchers#jsonPath(String, Object...)} or
* {@link MockMvcResultMatchers#jsonPath(String, Matcher)}.
*/
protected JsonPathResultMatchers(String expression, Object ... args) {
@@ -47,66 +49,133 @@ public class JsonPathResultMatchers {
/**
* Evaluate the JSONPath and assert the value of the content found with the
* given Hamcrest {@code Matcher}.
* Evaluate the JSON path expression against the response content and
* assert the resulting value with the given Hamcrest {@link Matcher}.
*/
public <T> ResultMatcher value(final Matcher<T> matcher) {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
jsonPathHelper.assertValue(content, matcher);
JsonPathResultMatchers.this.jsonPathHelper.assertValue(content, matcher);
}
};
}
/**
* Evaluate the JSONPath and assert the value of the content found.
* Evaluate the JSON path expression against the response content and
* assert that the result is equal to the supplied value.
*/
public ResultMatcher value(final Object expectedValue) {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
jsonPathHelper.assertValue(result.getResponse().getContentAsString(), expectedValue);
JsonPathResultMatchers.this.jsonPathHelper.assertValue(result.getResponse().getContentAsString(),
expectedValue);
}
};
}
/**
* Evaluate the JSONPath and assert that content exists.
* 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).
*/
public ResultMatcher exists() {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
jsonPathHelper.exists(content);
JsonPathResultMatchers.this.jsonPathHelper.exists(content);
}
};
}
/**
* Evaluate the JSON path and assert not content was found.
* 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).
*/
public ResultMatcher doesNotExist() {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
jsonPathHelper.doesNotExist(content);
JsonPathResultMatchers.this.jsonPathHelper.doesNotExist(content);
}
};
}
/**
* Evluate the JSON path and assert the content found is an array.
* Evaluate the JSON path expression against the response content and
* assert that the result is a {@link String}.
* @since 4.2.1
*/
public ResultMatcher isString() {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
JsonPathResultMatchers.this.jsonPathHelper.assertValueIsString(content);
}
};
}
/**
* Evaluate the JSON path expression against the response content and
* assert that the result is a {@link Boolean}.
* @since 4.2.1
*/
public ResultMatcher isBoolean() {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
JsonPathResultMatchers.this.jsonPathHelper.assertValueIsBoolean(content);
}
};
}
/**
* Evaluate the JSON path expression against the response content and
* assert that the result is a {@link Number}.
* @since 4.2.1
*/
public ResultMatcher isNumber() {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
JsonPathResultMatchers.this.jsonPathHelper.assertValueIsNumber(content);
}
};
}
/**
* Evaluate the JSON path expression against the response content and
* assert that the result is an array.
*/
public ResultMatcher isArray() {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
jsonPathHelper.assertValueIsArray(content);
JsonPathResultMatchers.this.jsonPathHelper.assertValueIsArray(content);
}
};
}
/**
* Evaluate the JSON path expression against the response content and
* assert that the result is a {@link java.util.Map}.
* @since 4.2.1
*/
public ResultMatcher isMap() {
return new ResultMatcher() {
@Override
public void match(MvcResult result) throws Exception {
String content = result.getResponse().getContentAsString();
JsonPathResultMatchers.this.jsonPathHelper.assertValueIsMap(content);
}
};
}

View File

@@ -36,6 +36,7 @@ import static org.springframework.test.util.AssertionErrors.*;
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @author Sam Brannen
* @since 3.2
*/
public abstract class MockMvcResultMatchers {
@@ -80,7 +81,7 @@ public abstract class MockMvcResultMatchers {
/**
* Asserts the request was forwarded to the given URL.
* This methods accepts only exact matches.
* <p>This methods accepts only exact matches.
* @param expectedUrl the exact URL expected
*/
public static ResultMatcher forwardedUrl(final String expectedUrl) {
@@ -94,7 +95,8 @@ public abstract class MockMvcResultMatchers {
/**
* Asserts the request was forwarded to the given URL.
* This methods accepts {@link org.springframework.util.AntPathMatcher} expressions.
* <p>This methods accepts {@link org.springframework.util.AntPathMatcher}
* expressions.
* @param urlPattern an AntPath expression to match against
* @since 4.0
* @see org.springframework.util.AntPathMatcher
@@ -112,7 +114,7 @@ public abstract class MockMvcResultMatchers {
/**
* Asserts the request was redirected to the given URL.
* This methods accepts only exact matches.
* <p>This methods accepts only exact matches.
* @param expectedUrl the exact URL expected
*/
public static ResultMatcher redirectedUrl(final String expectedUrl) {
@@ -126,7 +128,8 @@ public abstract class MockMvcResultMatchers {
/**
* Asserts the request was redirected to the given URL.
* This methods accepts {@link org.springframework.util.AntPathMatcher} expressions.
* <p>This method accepts {@link org.springframework.util.AntPathMatcher}
* expressions.
* @param expectedUrl an AntPath expression to match against
* @see org.springframework.util.AntPathMatcher
* @since 4.0
@@ -164,12 +167,13 @@ public abstract class MockMvcResultMatchers {
}
/**
* Access to response body assertions using a <a
* href="http://goessner.net/articles/JsonPath/">JSONPath</a> expression to
* inspect a specific subset of the body. The JSON path expression can be a
* parameterized string using formatting specifiers as defined in
* Access to response body assertions using a
* <a href="https://github.com/jayway/JsonPath">JsonPath</a> expression
* to inspect a specific subset of the body.
* <p>The JSON path expression can be a parameterized string using
* formatting specifiers as defined in
* {@link String#format(String, Object...)}.
* @param expression the JSON path optionally parameterized with arguments
* @param expression the JSON path expression, optionally parameterized with arguments
* @param args arguments to parameterize the JSON path expression with
*/
public static JsonPathResultMatchers jsonPath(String expression, Object ... args) {
@@ -177,10 +181,10 @@ public abstract class MockMvcResultMatchers {
}
/**
* Access to response body assertions using a <a
* href="http://goessner.net/articles/JsonPath/">JSONPath</a> expression to
* inspect a specific subset of the body and a Hamcrest match for asserting
* the value found at the JSON path.
* Access to response body assertions using a
* <a href="https://github.com/jayway/JsonPath">JsonPath</a> expression
* to inspect a specific subset of the body and a Hamcrest matcher for
* asserting the value found at the JSON path.
* @param expression the JSON path expression
* @param matcher a matcher for the value expected at the JSON path
*/
@@ -189,11 +193,11 @@ public abstract class MockMvcResultMatchers {
}
/**
* Access to response body assertions using an XPath to inspect a specific
* subset of the body. The XPath expression can be a parameterized string
* using formatting specifiers as defined in
* {@link String#format(String, Object...)}.
* @param expression the XPath optionally parameterized with arguments
* Access to response body assertions using an XPath expression to
* inspect a specific subset of the body.
* <p>The XPath expression can be a parameterized string using formatting
* specifiers as defined in {@link String#format(String, Object...)}.
* @param expression the XPath expression, optionally parameterized with arguments
* @param args arguments to parameterize the XPath expression with
*/
public static XpathResultMatchers xpath(String expression, Object... args) throws XPathExpressionException {
@@ -201,11 +205,11 @@ public abstract class MockMvcResultMatchers {
}
/**
* Access to response body assertions using an XPath to inspect a specific
* subset of the body. The XPath expression can be a parameterized string
* using formatting specifiers as defined in
* {@link String#format(String, Object...)}.
* @param expression the XPath optionally parameterized with arguments
* Access to response body assertions using an XPath expression to
* inspect a specific subset of the body.
* <p>The XPath expression can be a parameterized string using formatting
* specifiers as defined in {@link String#format(String, Object...)}.
* @param expression the XPath expression, optionally parameterized with arguments
* @param namespaces namespaces referenced in the XPath expression
* @param args arguments to parameterize the XPath expression with
*/