diff --git a/spring-test/src/main/java/org/springframework/test/json/AbstractJsonContentAssert.java b/spring-test/src/main/java/org/springframework/test/json/AbstractJsonContentAssert.java
new file mode 100644
index 0000000000..4c1771de2c
--- /dev/null
+++ b/spring-test/src/main/java/org/springframework/test/json/AbstractJsonContentAssert.java
@@ -0,0 +1,528 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.json;
+
+import java.io.File;
+import java.io.InputStream;
+import java.nio.charset.Charset;
+import java.nio.file.Path;
+import java.util.function.Consumer;
+
+import com.jayway.jsonpath.JsonPath;
+import com.jayway.jsonpath.PathNotFoundException;
+import org.assertj.core.api.AbstractStringAssert;
+import org.assertj.core.api.AssertProvider;
+import org.assertj.core.error.BasicErrorMessageFactory;
+import org.assertj.core.internal.Failures;
+import org.skyscreamer.jsonassert.JSONCompare;
+import org.skyscreamer.jsonassert.JSONCompareMode;
+import org.skyscreamer.jsonassert.JSONCompareResult;
+import org.skyscreamer.jsonassert.comparator.JSONComparator;
+
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.InputStreamResource;
+import org.springframework.core.io.Resource;
+import org.springframework.http.converter.GenericHttpMessageConverter;
+import org.springframework.lang.Nullable;
+import org.springframework.util.Assert;
+import org.springframework.util.function.ThrowingBiFunction;
+
+/**
+ * Base AssertJ {@link org.assertj.core.api.Assert assertions} that can be
+ * applied to a JSON document.
+ *
+ *
Support evaluating {@linkplain JsonPath JSON path} expressions and
+ * extracting a part of the document for further {@linkplain JsonPathValueAssert
+ * assertions} on the value.
+ *
+ *
Also support comparing the JSON document against a target, using
+ * {@linkplain JSONCompare JSON Assert}. Resources that are loaded from
+ * the classpath can be relative if a {@linkplain #withResourceLoadClass(Class)
+ * class} is provided. By default, {@code UTF-8} is used to load resources
+ * but this can be overridden using {@link #withCharset(Charset)}.
+ *
+ * @author Stephane Nicoll
+ * @author Phillip Webb
+ * @author Andy Wilkinson
+ * @author Diego Berrueta
+ * @author Camille Vienot
+ * @since 6.2
+ * @param the type of assertions
+ */
+public abstract class AbstractJsonContentAssert>
+ extends AbstractStringAssert {
+
+ private static final Failures failures = Failures.instance();
+
+
+ @Nullable
+ private final GenericHttpMessageConverter jsonMessageConverter;
+
+ @Nullable
+ private Class> resourceLoadClass;
+
+ @Nullable
+ private Charset charset;
+
+ private JsonLoader jsonLoader;
+
+ /**
+ * Create an assert for the given JSON document.
+ * Path can be converted to a value object using the given
+ * {@linkplain GenericHttpMessageConverter json message converter}.
+ * @param json the JSON document to assert
+ * @param jsonMessageConverter the converter to use
+ * @param selfType the implementation type of this assert
+ */
+ protected AbstractJsonContentAssert(@Nullable String json,
+ @Nullable GenericHttpMessageConverter jsonMessageConverter, Class> selfType) {
+ super(json, selfType);
+ this.jsonMessageConverter = jsonMessageConverter;
+ this.jsonLoader = new JsonLoader(null, null);
+ as("JSON content");
+ }
+
+ // JsonPath support
+
+ /**
+ * Verify that the given JSON {@code path} is present, and extract the JSON
+ * value for further {@linkplain JsonPathValueAssert assertions}.
+ * @param path the {@link JsonPath} expression
+ * @see #hasPathSatisfying(String, Consumer)
+ */
+ public JsonPathValueAssert extractingPath(String path) {
+ Object value = new JsonPathValue(path).getValue();
+ return new JsonPathValueAssert(value, path, this.jsonMessageConverter);
+ }
+
+ /**
+ * Verify that the given JSON {@code path} is present with a JSON value
+ * satisfying the given {@code valueRequirements}.
+ * @param path the {@link JsonPath} expression
+ * @param valueRequirements a {@link Consumer} of the assertion object
+ */
+ public SELF hasPathSatisfying(String path, Consumer> valueRequirements) {
+ Object value = new JsonPathValue(path).assertHasPath();
+ JsonPathValueAssert valueAssert = new JsonPathValueAssert(value, path, this.jsonMessageConverter);
+ valueRequirements.accept(() -> valueAssert);
+ return this.myself;
+ }
+
+ /**
+ * Verify that the given JSON {@code path} matches. For paths with an
+ * operator, this validates that the path expression is valid, but does not
+ * validate that it yield any results.
+ * @param path the {@link JsonPath} expression
+ */
+ public SELF hasPath(String path) {
+ new JsonPathValue(path).assertHasPath();
+ return this.myself;
+ }
+
+ /**
+ * Verify that the given JSON {@code path} does not match.
+ * @param path the {@link JsonPath} expression
+ */
+ public SELF doesNotHavePath(String path) {
+ new JsonPathValue(path).assertDoesNotHavePath();
+ return this.myself;
+ }
+
+ // JsonAssert support
+
+ /**
+ * Verify that the actual value is equal to the given JSON. The
+ * {@code expected} value can contain the JSON itself or, if it ends with
+ * {@code .json}, the name of a resource to be loaded from the classpath.
+ * @param expected the expected JSON or the name of a resource containing
+ * the expected JSON
+ * @param compareMode the compare mode used when checking
+ */
+ public SELF isEqualTo(@Nullable CharSequence expected, JSONCompareMode compareMode) {
+ String expectedJson = this.jsonLoader.getJson(expected);
+ return assertNotFailed(compare(expectedJson, compareMode));
+ }
+
+ /**
+ * Verify that the actual value is equal to the given JSON {@link Resource}.
+ * The resource abstraction allows to provide several input types:
+ *
+ * a {@code byte} array, using {@link ByteArrayResource}
+ * a {@code classpath} resource, using {@link ClassPathResource}
+ * a {@link File} or {@link Path}, using {@link FileSystemResource}
+ * an {@link InputStream}, using {@link InputStreamResource}
+ *
+ * @param expected a resource containing the expected JSON
+ * @param compareMode the compare mode used when checking
+ */
+ public SELF isEqualTo(Resource expected, JSONCompareMode compareMode) {
+ String expectedJson = this.jsonLoader.getJson(expected);
+ return assertNotFailed(compare(expectedJson, compareMode));
+ }
+
+ /**
+ * Verify that the actual value is equal to the given JSON. The
+ * {@code expected} value can contain the JSON itself or, if it ends with
+ * {@code .json}, the name of a resource to be loaded from the classpath.
+ * @param expected the expected JSON or the name of a resource containing
+ * the expected JSON
+ * @param comparator the comparator used when checking
+ */
+ public SELF isEqualTo(@Nullable CharSequence expected, JSONComparator comparator) {
+ String expectedJson = this.jsonLoader.getJson(expected);
+ return assertNotFailed(compare(expectedJson, comparator));
+ }
+
+ /**
+ * Verify that the actual value is equal to the given JSON {@link Resource}.
+ * The resource abstraction allows to provide several input types:
+ *
+ * a {@code byte} array, using {@link ByteArrayResource}
+ * a {@code classpath} resource, using {@link ClassPathResource}
+ * a {@link File} or {@link Path}, using {@link FileSystemResource}
+ * an {@link InputStream}, using {@link InputStreamResource}
+ *
+ * @param expected a resource containing the expected JSON
+ * @param comparator the comparator used when checking
+ */
+ public SELF isEqualTo(Resource expected, JSONComparator comparator) {
+ String expectedJson = this.jsonLoader.getJson(expected);
+ return assertNotFailed(compare(expectedJson, comparator));
+ }
+
+ /**
+ * Verify that the actual value is {@link JSONCompareMode#LENIENT leniently}
+ * equal to the given JSON. The {@code expected} value can contain the JSON
+ * itself or, if it ends with {@code .json}, the name of a resource to be
+ * loaded from the classpath.
+ * @param expected the expected JSON or the name of a resource containing
+ * the expected JSON
+ */
+ public SELF isLenientlyEqualTo(@Nullable CharSequence expected) {
+ return isEqualTo(expected, JSONCompareMode.LENIENT);
+ }
+
+ /**
+ * Verify that the actual value is {@link JSONCompareMode#LENIENT leniently}
+ * equal to the given JSON {@link Resource}.
+ * The resource abstraction allows to provide several input types:
+ *
+ * a {@code byte} array, using {@link ByteArrayResource}
+ * a {@code classpath} resource, using {@link ClassPathResource}
+ * a {@link File} or {@link Path}, using {@link FileSystemResource}
+ * an {@link InputStream}, using {@link InputStreamResource}
+ *
+ * @param expected a resource containing the expected JSON
+ */
+ public SELF isLenientlyEqualTo(Resource expected) {
+ return isEqualTo(expected, JSONCompareMode.LENIENT);
+ }
+
+ /**
+ * Verify that the actual value is {@link JSONCompareMode#STRICT strictly}
+ * equal to the given JSON. The {@code expected} value can contain the JSON
+ * itself or, if it ends with {@code .json}, the name of a resource to be
+ * loaded from the classpath.
+ * @param expected the expected JSON or the name of a resource containing
+ * the expected JSON
+ */
+ public SELF isStrictlyEqualTo(@Nullable CharSequence expected) {
+ return isEqualTo(expected, JSONCompareMode.STRICT);
+ }
+
+ /**
+ * Verify that the actual value is {@link JSONCompareMode#STRICT strictly}
+ * equal to the given JSON {@link Resource}.
+ * The resource abstraction allows to provide several input types:
+ *
+ * a {@code byte} array, using {@link ByteArrayResource}
+ * a {@code classpath} resource, using {@link ClassPathResource}
+ * a {@link File} or {@link Path}, using {@link FileSystemResource}
+ * an {@link InputStream}, using {@link InputStreamResource}
+ *
+ * @param expected a resource containing the expected JSON
+ */
+ public SELF isStrictlyEqualTo(Resource expected) {
+ return isEqualTo(expected, JSONCompareMode.STRICT);
+ }
+
+ /**
+ * Verify that the actual value is not equal to the given JSON. The
+ * {@code expected} value can contain the JSON itself or, if it ends with
+ * {@code .json}, the name of a resource to be loaded from the classpath.
+ * @param expected the expected JSON or the name of a resource containing
+ * the expected JSON
+ * @param compareMode the compare mode used when checking
+ */
+ public SELF isNotEqualTo(@Nullable CharSequence expected, JSONCompareMode compareMode) {
+ String expectedJson = this.jsonLoader.getJson(expected);
+ return assertNotPassed(compare(expectedJson, compareMode));
+ }
+
+ /**
+ * Verify that the actual value is not equal to the given JSON {@link Resource}.
+ * The resource abstraction allows to provide several input types:
+ *
+ * a {@code byte} array, using {@link ByteArrayResource}
+ * a {@code classpath} resource, using {@link ClassPathResource}
+ * a {@link File} or {@link Path}, using {@link FileSystemResource}
+ * an {@link InputStream}, using {@link InputStreamResource}
+ *
+ * @param expected a resource containing the expected JSON
+ * @param compareMode the compare mode used when checking
+ */
+ public SELF isNotEqualTo(Resource expected, JSONCompareMode compareMode) {
+ String expectedJson = this.jsonLoader.getJson(expected);
+ return assertNotPassed(compare(expectedJson, compareMode));
+ }
+
+ /**
+ * Verify that the actual value is not equal to the given JSON. The
+ * {@code expected} value can contain the JSON itself or, if it ends with
+ * {@code .json}, the name of a resource to be loaded from the classpath.
+ * @param expected the expected JSON or the name of a resource containing
+ * the expected JSON
+ * @param comparator the comparator used when checking
+ */
+ public SELF isNotEqualTo(@Nullable CharSequence expected, JSONComparator comparator) {
+ String expectedJson = this.jsonLoader.getJson(expected);
+ return assertNotPassed(compare(expectedJson, comparator));
+ }
+
+ /**
+ * Verify that the actual value is not equal to the given JSON {@link Resource}.
+ * The resource abstraction allows to provide several input types:
+ *
+ * a {@code byte} array, using {@link ByteArrayResource}
+ * a {@code classpath} resource, using {@link ClassPathResource}
+ * a {@link File} or {@link Path}, using {@link FileSystemResource}
+ * an {@link InputStream}, using {@link InputStreamResource}
+ *
+ * @param expected a resource containing the expected JSON
+ * @param comparator the comparator used when checking
+ */
+ public SELF isNotEqualTo(Resource expected, JSONComparator comparator) {
+ String expectedJson = this.jsonLoader.getJson(expected);
+ return assertNotPassed(compare(expectedJson, comparator));
+ }
+
+ /**
+ * Verify that the actual value is not {@link JSONCompareMode#LENIENT
+ * leniently} equal to the given JSON. The {@code expected} value can
+ * contain the JSON itself or, if it ends with {@code .json}, the name of a
+ * resource to be loaded from the classpath.
+ * @param expected the expected JSON or the name of a resource containing
+ * the expected JSON
+ */
+ public SELF isNotLenientlyEqualTo(@Nullable CharSequence expected) {
+ return isNotEqualTo(expected, JSONCompareMode.LENIENT);
+ }
+
+ /**
+ * Verify that the actual value is not {@link JSONCompareMode#LENIENT
+ * leniently} equal to the given JSON {@link Resource}.
+ * The resource abstraction allows to provide several input types:
+ *
+ * a {@code byte} array, using {@link ByteArrayResource}
+ * a {@code classpath} resource, using {@link ClassPathResource}
+ * a {@link File} or {@link Path}, using {@link FileSystemResource}
+ * an {@link InputStream}, using {@link InputStreamResource}
+ *
+ * @param expected a resource containing the expected JSON
+ */
+ public SELF isNotLenientlyEqualTo(Resource expected) {
+ return isNotEqualTo(expected, JSONCompareMode.LENIENT);
+ }
+
+ /**
+ * Verify that the actual value is not {@link JSONCompareMode#STRICT
+ * strictly} equal to the given JSON. The {@code expected} value can
+ * contain the JSON itself or, if it ends with {@code .json}, the name of a
+ * resource to be loaded from the classpath.
+ * @param expected the expected JSON or the name of a resource containing
+ * the expected JSON
+ */
+ public SELF isNotStrictlyEqualTo(@Nullable CharSequence expected) {
+ return isNotEqualTo(expected, JSONCompareMode.STRICT);
+ }
+
+ /**
+ * Verify that the actual value is not {@link JSONCompareMode#STRICT
+ * strictly} equal to the given JSON {@link Resource}.
+ * The resource abstraction allows to provide several input types:
+ *
+ * a {@code byte} array, using {@link ByteArrayResource}
+ * a {@code classpath} resource, using {@link ClassPathResource}
+ * a {@link File} or {@link Path}, using {@link FileSystemResource}
+ * an {@link InputStream}, using {@link InputStreamResource}
+ *
+ * @param expected a resource containing the expected JSON
+ */
+ public SELF isNotStrictlyEqualTo(Resource expected) {
+ return isNotEqualTo(expected, JSONCompareMode.STRICT);
+ }
+
+ /**
+ * Override the class used to load resources. Resources can be loaded from
+ * an absolute location or relative to tpe specified class. For instance,
+ * specifying {@code com.example.MyClass} as the resource class allows you
+ * to use "my-file.json" to load {@code /com/example/my-file.json}.
+ * @param resourceLoadClass the class used to load resources or {@code null}
+ * to only use absolute paths.
+ */
+ public SELF withResourceLoadClass(@Nullable Class> resourceLoadClass) {
+ this.resourceLoadClass = resourceLoadClass;
+ this.jsonLoader = new JsonLoader(resourceLoadClass, this.charset);
+ return this.myself;
+ }
+
+ /**
+ * Override the {@link Charset} to use to load resources. By default,
+ * resources are loaded using {@code UTF-8}.
+ * @param charset the charset to use, or {@code null} to use the default
+ */
+ public SELF withCharset(@Nullable Charset charset) {
+ this.charset = charset;
+ this.jsonLoader = new JsonLoader(this.resourceLoadClass, charset);
+ return this.myself;
+ }
+
+
+ private JSONCompareResult compare(@Nullable CharSequence expectedJson, JSONCompareMode compareMode) {
+ return compare(this.actual, expectedJson, (actualJsonString, expectedJsonString) ->
+ JSONCompare.compareJSON(expectedJsonString, actualJsonString, compareMode));
+ }
+
+ private JSONCompareResult compare(@Nullable CharSequence expectedJson, JSONComparator comparator) {
+ return compare(this.actual, expectedJson, (actualJsonString, expectedJsonString) ->
+ JSONCompare.compareJSON(expectedJsonString, actualJsonString, comparator));
+ }
+
+ private JSONCompareResult compare(@Nullable CharSequence actualJson, @Nullable CharSequence expectedJson,
+ ThrowingBiFunction comparator) {
+
+ if (actualJson == null) {
+ return compareForNull(expectedJson);
+ }
+ if (expectedJson == null) {
+ return compareForNull(actualJson.toString());
+ }
+ try {
+ return comparator.applyWithException(actualJson.toString(), expectedJson.toString());
+ }
+ catch (Exception ex) {
+ if (ex instanceof RuntimeException runtimeException) {
+ throw runtimeException;
+ }
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ private JSONCompareResult compareForNull(@Nullable CharSequence expectedJson) {
+ JSONCompareResult result = new JSONCompareResult();
+ if (expectedJson != null) {
+ result.fail("Expected null JSON");
+ }
+ return result;
+ }
+
+ private SELF assertNotFailed(JSONCompareResult result) {
+ if (result.failed()) {
+ failWithMessage("JSON comparison failure: %s", result.getMessage());
+ }
+ return this.myself;
+ }
+
+ private SELF assertNotPassed(JSONCompareResult result) {
+ if (result.passed()) {
+ failWithMessage("JSON comparison failure: %s", result.getMessage());
+ }
+ return this.myself;
+ }
+
+ private AssertionError failure(BasicErrorMessageFactory errorMessageFactory) {
+ throw failures.failure(this.info, errorMessageFactory);
+ }
+
+
+ /**
+ * A {@link JsonPath} value.
+ */
+ private class JsonPathValue {
+
+ private final String path;
+
+ private final JsonPath jsonPath;
+
+ private final String json;
+
+ JsonPathValue(String path) {
+ Assert.hasText(path, "'path' must not be null or empty");
+ isNotNull();
+ this.path = path;
+ this.jsonPath = JsonPath.compile(this.path);
+ this.json = AbstractJsonContentAssert.this.actual;
+ }
+
+ @Nullable
+ Object assertHasPath() {
+ return getValue();
+ }
+
+ void assertDoesNotHavePath() {
+ try {
+ read();
+ throw failure(new JsonPathNotExpected(this.json, this.path));
+ }
+ catch (PathNotFoundException ignore) {
+ }
+ }
+
+ @Nullable
+ Object getValue() {
+ try {
+ return read();
+ }
+ catch (PathNotFoundException ex) {
+ throw failure(new JsonPathNotFound(this.json, this.path));
+ }
+ }
+
+ @Nullable
+ private Object read() {
+ return this.jsonPath.read(this.json);
+ }
+
+
+ static final class JsonPathNotFound extends BasicErrorMessageFactory {
+
+ private JsonPathNotFound(String actual, String path) {
+ super("%nExpecting:%n %s%nTo match JSON path:%n %s%n", actual, path);
+ }
+ }
+
+ static final class JsonPathNotExpected extends BasicErrorMessageFactory {
+
+ private JsonPathNotExpected(String actual, String path) {
+ super("%nExpecting:%n %s%nNot to match JSON path:%n %s%n", actual, path);
+ }
+ }
+ }
+
+}
diff --git a/spring-test/src/main/java/org/springframework/test/json/JsonContent.java b/spring-test/src/main/java/org/springframework/test/json/JsonContent.java
index a81c4724e1..c801a5fa5d 100644
--- a/spring-test/src/main/java/org/springframework/test/json/JsonContent.java
+++ b/spring-test/src/main/java/org/springframework/test/json/JsonContent.java
@@ -55,7 +55,7 @@ public final class JsonContent implements AssertProvider {
*/
@Override
public JsonContentAssert assertThat() {
- return new JsonContentAssert(this.json, this.resourceLoadClass, null);
+ return new JsonContentAssert(this.json, null).withResourceLoadClass(this.resourceLoadClass);
}
/**
diff --git a/spring-test/src/main/java/org/springframework/test/json/JsonContentAssert.java b/spring-test/src/main/java/org/springframework/test/json/JsonContentAssert.java
index 391c466c3f..db0212feeb 100644
--- a/spring-test/src/main/java/org/springframework/test/json/JsonContentAssert.java
+++ b/spring-test/src/main/java/org/springframework/test/json/JsonContentAssert.java
@@ -16,351 +16,26 @@
package org.springframework.test.json;
-import java.io.File;
-import java.io.InputStream;
-import java.nio.charset.Charset;
-import java.nio.file.Path;
-
-import org.assertj.core.api.AbstractAssert;
-import org.skyscreamer.jsonassert.JSONCompare;
-import org.skyscreamer.jsonassert.JSONCompareMode;
-import org.skyscreamer.jsonassert.JSONCompareResult;
-import org.skyscreamer.jsonassert.comparator.JSONComparator;
-
-import org.springframework.core.io.ByteArrayResource;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.FileSystemResource;
-import org.springframework.core.io.InputStreamResource;
-import org.springframework.core.io.Resource;
+import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.lang.Nullable;
-import org.springframework.util.function.ThrowingBiFunction;
/**
- * AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied
- * to a {@link CharSequence} representation of a JSON document, mostly to
- * compare the JSON document against a target, using {@linkplain JSONCompare
- * JSON Assert}.
+ * Default {@link AbstractJsonContentAssert} implementation.
*
- * @author Phillip Webb
- * @author Andy Wilkinson
- * @author Diego Berrueta
- * @author Camille Vienot
* @author Stephane Nicoll
* @since 6.2
*/
-public class JsonContentAssert extends AbstractAssert {
-
- private final JsonLoader loader;
+public class JsonContentAssert extends AbstractJsonContentAssert {
/**
- * Create a new {@link JsonContentAssert} instance that will load resources
- * relative to the given {@code resourceLoadClass}, using the given
- * {@code charset}.
- * @param json the actual JSON content
- * @param resourceLoadClass the class used to load resources
- * @param charset the charset of the JSON resources
+ * Create an assert for the given JSON document.
+ * Path can be converted to a value object using the given
+ * {@linkplain GenericHttpMessageConverter json message converter}.
+ * @param json the JSON document to assert
+ * @param jsonMessageConverter the converter to use
*/
- public JsonContentAssert(@Nullable CharSequence json, @Nullable Class> resourceLoadClass,
- @Nullable Charset charset) {
-
- super(json, JsonContentAssert.class);
- this.loader = new JsonLoader(resourceLoadClass, charset);
- }
-
- /**
- * Create a new {@link JsonContentAssert} instance that will load resources
- * relative to the given {@code resourceLoadClass}, using {@code UTF-8}.
- * @param json the actual JSON content
- * @param resourceLoadClass the class used to load resources
- */
- public JsonContentAssert(@Nullable CharSequence json, @Nullable Class> resourceLoadClass) {
- this(json, resourceLoadClass, null);
- }
-
-
- /**
- * Verify that the actual value is equal to the given JSON. The
- * {@code expected} value can contain the JSON itself or, if it ends with
- * {@code .json}, the name of a resource to be loaded from the classpath.
- * @param expected the expected JSON or the name of a resource containing
- * the expected JSON
- * @param compareMode the compare mode used when checking
- */
- public JsonContentAssert isEqualTo(@Nullable CharSequence expected, JSONCompareMode compareMode) {
- String expectedJson = this.loader.getJson(expected);
- return assertNotFailed(compare(expectedJson, compareMode));
- }
-
- /**
- * Verify that the actual value is equal to the given JSON {@link Resource}.
- *
The resource abstraction allows to provide several input types:
- *
- * a {@code byte} array, using {@link ByteArrayResource}
- * a {@code classpath} resource, using {@link ClassPathResource}
- * a {@link File} or {@link Path}, using {@link FileSystemResource}
- * an {@link InputStream}, using {@link InputStreamResource}
- *
- * @param expected a resource containing the expected JSON
- * @param compareMode the compare mode used when checking
- */
- public JsonContentAssert isEqualTo(Resource expected, JSONCompareMode compareMode) {
- String expectedJson = this.loader.getJson(expected);
- return assertNotFailed(compare(expectedJson, compareMode));
- }
-
- /**
- * Verify that the actual value is equal to the given JSON. The
- * {@code expected} value can contain the JSON itself or, if it ends with
- * {@code .json}, the name of a resource to be loaded from the classpath.
- * @param expected the expected JSON or the name of a resource containing
- * the expected JSON
- * @param comparator the comparator used when checking
- */
- public JsonContentAssert isEqualTo(@Nullable CharSequence expected, JSONComparator comparator) {
- String expectedJson = this.loader.getJson(expected);
- return assertNotFailed(compare(expectedJson, comparator));
- }
-
- /**
- * Verify that the actual value is equal to the given JSON {@link Resource}.
- * The resource abstraction allows to provide several input types:
- *
- * a {@code byte} array, using {@link ByteArrayResource}
- * a {@code classpath} resource, using {@link ClassPathResource}
- * a {@link File} or {@link Path}, using {@link FileSystemResource}
- * an {@link InputStream}, using {@link InputStreamResource}
- *
- * @param expected a resource containing the expected JSON
- * @param comparator the comparator used when checking
- */
- public JsonContentAssert isEqualTo(Resource expected, JSONComparator comparator) {
- String expectedJson = this.loader.getJson(expected);
- return assertNotFailed(compare(expectedJson, comparator));
- }
-
- /**
- * Verify that the actual value is {@link JSONCompareMode#LENIENT leniently}
- * equal to the given JSON. The {@code expected} value can contain the JSON
- * itself or, if it ends with {@code .json}, the name of a resource to be
- * loaded from the classpath.
- * @param expected the expected JSON or the name of a resource containing
- * the expected JSON
- */
- public JsonContentAssert isLenientlyEqualTo(@Nullable CharSequence expected) {
- return isEqualTo(expected, JSONCompareMode.LENIENT);
- }
-
- /**
- * Verify that the actual value is {@link JSONCompareMode#LENIENT leniently}
- * equal to the given JSON {@link Resource}.
- * The resource abstraction allows to provide several input types:
- *
- * a {@code byte} array, using {@link ByteArrayResource}
- * a {@code classpath} resource, using {@link ClassPathResource}
- * a {@link File} or {@link Path}, using {@link FileSystemResource}
- * an {@link InputStream}, using {@link InputStreamResource}
- *
- * @param expected a resource containing the expected JSON
- */
- public JsonContentAssert isLenientlyEqualTo(Resource expected) {
- return isEqualTo(expected, JSONCompareMode.LENIENT);
- }
-
- /**
- * Verify that the actual value is {@link JSONCompareMode#STRICT strictly}
- * equal to the given JSON. The {@code expected} value can contain the JSON
- * itself or, if it ends with {@code .json}, the name of a resource to be
- * loaded from the classpath.
- * @param expected the expected JSON or the name of a resource containing
- * the expected JSON
- */
- public JsonContentAssert isStrictlyEqualTo(@Nullable CharSequence expected) {
- return isEqualTo(expected, JSONCompareMode.STRICT);
- }
-
- /**
- * Verify that the actual value is {@link JSONCompareMode#STRICT strictly}
- * equal to the given JSON {@link Resource}.
- * The resource abstraction allows to provide several input types:
- *
- * a {@code byte} array, using {@link ByteArrayResource}
- * a {@code classpath} resource, using {@link ClassPathResource}
- * a {@link File} or {@link Path}, using {@link FileSystemResource}
- * an {@link InputStream}, using {@link InputStreamResource}
- *
- * @param expected a resource containing the expected JSON
- */
- public JsonContentAssert isStrictlyEqualTo(Resource expected) {
- return isEqualTo(expected, JSONCompareMode.STRICT);
- }
-
- /**
- * Verify that the actual value is not equal to the given JSON. The
- * {@code expected} value can contain the JSON itself or, if it ends with
- * {@code .json}, the name of a resource to be loaded from the classpath.
- * @param expected the expected JSON or the name of a resource containing
- * the expected JSON
- * @param compareMode the compare mode used when checking
- */
- public JsonContentAssert isNotEqualTo(@Nullable CharSequence expected, JSONCompareMode compareMode) {
- String expectedJson = this.loader.getJson(expected);
- return assertNotPassed(compare(expectedJson, compareMode));
- }
-
- /**
- * Verify that the actual value is not equal to the given JSON {@link Resource}.
- * The resource abstraction allows to provide several input types:
- *
- * a {@code byte} array, using {@link ByteArrayResource}
- * a {@code classpath} resource, using {@link ClassPathResource}
- * a {@link File} or {@link Path}, using {@link FileSystemResource}
- * an {@link InputStream}, using {@link InputStreamResource}
- *
- * @param expected a resource containing the expected JSON
- * @param compareMode the compare mode used when checking
- */
- public JsonContentAssert isNotEqualTo(Resource expected, JSONCompareMode compareMode) {
- String expectedJson = this.loader.getJson(expected);
- return assertNotPassed(compare(expectedJson, compareMode));
- }
-
- /**
- * Verify that the actual value is not equal to the given JSON. The
- * {@code expected} value can contain the JSON itself or, if it ends with
- * {@code .json}, the name of a resource to be loaded from the classpath.
- * @param expected the expected JSON or the name of a resource containing
- * the expected JSON
- * @param comparator the comparator used when checking
- */
- public JsonContentAssert isNotEqualTo(@Nullable CharSequence expected, JSONComparator comparator) {
- String expectedJson = this.loader.getJson(expected);
- return assertNotPassed(compare(expectedJson, comparator));
- }
-
- /**
- * Verify that the actual value is not equal to the given JSON {@link Resource}.
- * The resource abstraction allows to provide several input types:
- *
- * a {@code byte} array, using {@link ByteArrayResource}
- * a {@code classpath} resource, using {@link ClassPathResource}
- * a {@link File} or {@link Path}, using {@link FileSystemResource}
- * an {@link InputStream}, using {@link InputStreamResource}
- *
- * @param expected a resource containing the expected JSON
- * @param comparator the comparator used when checking
- */
- public JsonContentAssert isNotEqualTo(Resource expected, JSONComparator comparator) {
- String expectedJson = this.loader.getJson(expected);
- return assertNotPassed(compare(expectedJson, comparator));
- }
-
- /**
- * Verify that the actual value is not {@link JSONCompareMode#LENIENT
- * leniently} equal to the given JSON. The {@code expected} value can
- * contain the JSON itself or, if it ends with {@code .json}, the name of a
- * resource to be loaded from the classpath.
- * @param expected the expected JSON or the name of a resource containing
- * the expected JSON
- */
- public JsonContentAssert isNotLenientlyEqualTo(@Nullable CharSequence expected) {
- return isNotEqualTo(expected, JSONCompareMode.LENIENT);
- }
-
- /**
- * Verify that the actual value is not {@link JSONCompareMode#LENIENT
- * leniently} equal to the given JSON {@link Resource}.
- * The resource abstraction allows to provide several input types:
- *
- * a {@code byte} array, using {@link ByteArrayResource}
- * a {@code classpath} resource, using {@link ClassPathResource}
- * a {@link File} or {@link Path}, using {@link FileSystemResource}
- * an {@link InputStream}, using {@link InputStreamResource}
- *
- * @param expected a resource containing the expected JSON
- */
- public JsonContentAssert isNotLenientlyEqualTo(Resource expected) {
- return isNotEqualTo(expected, JSONCompareMode.LENIENT);
- }
-
- /**
- * Verify that the actual value is not {@link JSONCompareMode#STRICT
- * strictly} equal to the given JSON. The {@code expected} value can
- * contain the JSON itself or, if it ends with {@code .json}, the name of a
- * resource to be loaded from the classpath.
- * @param expected the expected JSON or the name of a resource containing
- * the expected JSON
- */
- public JsonContentAssert isNotStrictlyEqualTo(@Nullable CharSequence expected) {
- return isNotEqualTo(expected, JSONCompareMode.STRICT);
- }
-
- /**
- * Verify that the actual value is not {@link JSONCompareMode#STRICT
- * strictly} equal to the given JSON {@link Resource}.
- * The resource abstraction allows to provide several input types:
- *
- * a {@code byte} array, using {@link ByteArrayResource}
- * a {@code classpath} resource, using {@link ClassPathResource}
- * a {@link File} or {@link Path}, using {@link FileSystemResource}
- * an {@link InputStream}, using {@link InputStreamResource}
- *
- * @param expected a resource containing the expected JSON
- */
- public JsonContentAssert isNotStrictlyEqualTo(Resource expected) {
- return isNotEqualTo(expected, JSONCompareMode.STRICT);
- }
-
-
- private JSONCompareResult compare(@Nullable CharSequence expectedJson, JSONCompareMode compareMode) {
- return compare(this.actual, expectedJson, (actualJsonString, expectedJsonString) ->
- JSONCompare.compareJSON(expectedJsonString, actualJsonString, compareMode));
- }
-
- private JSONCompareResult compare(@Nullable CharSequence expectedJson, JSONComparator comparator) {
- return compare(this.actual, expectedJson, (actualJsonString, expectedJsonString) ->
- JSONCompare.compareJSON(expectedJsonString, actualJsonString, comparator));
- }
-
- private JSONCompareResult compare(@Nullable CharSequence actualJson, @Nullable CharSequence expectedJson,
- ThrowingBiFunction comparator) {
-
- if (actualJson == null) {
- return compareForNull(expectedJson);
- }
- if (expectedJson == null) {
- return compareForNull(actualJson.toString());
- }
- try {
- return comparator.applyWithException(actualJson.toString(), expectedJson.toString());
- }
- catch (Exception ex) {
- if (ex instanceof RuntimeException runtimeException) {
- throw runtimeException;
- }
- throw new IllegalStateException(ex);
- }
- }
-
- private JSONCompareResult compareForNull(@Nullable CharSequence expectedJson) {
- JSONCompareResult result = new JSONCompareResult();
- if (expectedJson != null) {
- result.fail("Expected null JSON");
- }
- return result;
- }
-
- private JsonContentAssert assertNotFailed(JSONCompareResult result) {
- if (result.failed()) {
- failWithMessage("JSON comparison failure: %s", result.getMessage());
- }
- return this;
- }
-
- private JsonContentAssert assertNotPassed(JSONCompareResult result) {
- if (result.passed()) {
- failWithMessage("JSON comparison failure: %s", result.getMessage());
- }
- return this;
+ public JsonContentAssert(@Nullable String json, @Nullable GenericHttpMessageConverter jsonMessageConverter) {
+ super(json, jsonMessageConverter, JsonContentAssert.class);
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/json/JsonPathAssert.java b/spring-test/src/main/java/org/springframework/test/json/JsonPathAssert.java
deleted file mode 100644
index 2ba28fcafd..0000000000
--- a/spring-test/src/main/java/org/springframework/test/json/JsonPathAssert.java
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Copyright 2002-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.test.json;
-
-import java.util.function.Consumer;
-
-import com.jayway.jsonpath.JsonPath;
-import com.jayway.jsonpath.PathNotFoundException;
-import org.assertj.core.api.AbstractAssert;
-import org.assertj.core.api.AssertProvider;
-import org.assertj.core.error.BasicErrorMessageFactory;
-import org.assertj.core.internal.Failures;
-
-import org.springframework.http.converter.GenericHttpMessageConverter;
-import org.springframework.lang.Nullable;
-import org.springframework.util.Assert;
-
-/**
- * AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied
- * to a {@link CharSequence} representation of a JSON document using
- * {@linkplain JsonPath JSON path}.
- *
- * @author Stephane Nicoll
- * @since 6.2
- */
-public class JsonPathAssert extends AbstractAssert {
-
- private static final Failures failures = Failures.instance();
-
-
- @Nullable
- private final GenericHttpMessageConverter jsonMessageConverter;
-
-
- public JsonPathAssert(CharSequence json,
- @Nullable GenericHttpMessageConverter jsonMessageConverter) {
-
- super(json, JsonPathAssert.class);
- this.jsonMessageConverter = jsonMessageConverter;
- }
-
-
- /**
- * Verify that the given JSON {@code path} is present, and extract the JSON
- * value for further {@linkplain JsonPathValueAssert assertions}.
- * @param path the {@link JsonPath} expression
- * @see #hasPathSatisfying(String, Consumer)
- */
- public JsonPathValueAssert extractingPath(String path) {
- Object value = new JsonPathValue(path).getValue();
- return new JsonPathValueAssert(value, path, this.jsonMessageConverter);
- }
-
- /**
- * Verify that the given JSON {@code path} is present with a JSON value
- * satisfying the given {@code valueRequirements}.
- * @param path the {@link JsonPath} expression
- * @param valueRequirements a {@link Consumer} of the assertion object
- */
- public JsonPathAssert hasPathSatisfying(String path, Consumer> valueRequirements) {
- Object value = new JsonPathValue(path).assertHasPath();
- JsonPathValueAssert valueAssert = new JsonPathValueAssert(value, path, this.jsonMessageConverter);
- valueRequirements.accept(() -> valueAssert);
- return this;
- }
-
- /**
- * Verify that the given JSON {@code path} matches. For paths with an
- * operator, this validates that the path expression is valid, but does not
- * validate that it yield any results.
- * @param path the {@link JsonPath} expression
- */
- public JsonPathAssert hasPath(String path) {
- new JsonPathValue(path).assertHasPath();
- return this;
- }
-
- /**
- * Verify that the given JSON {@code path} does not match.
- * @param path the {@link JsonPath} expression
- */
- public JsonPathAssert doesNotHavePath(String path) {
- new JsonPathValue(path).assertDoesNotHavePath();
- return this;
- }
-
-
- private AssertionError failure(BasicErrorMessageFactory errorMessageFactory) {
- throw failures.failure(this.info, errorMessageFactory);
- }
-
-
- /**
- * A {@link JsonPath} value.
- */
- private class JsonPathValue {
-
- private final String path;
-
- private final JsonPath jsonPath;
-
- private final String json;
-
- JsonPathValue(String path) {
- Assert.hasText(path, "'path' must not be null or empty");
- this.path = path;
- this.jsonPath = JsonPath.compile(this.path);
- this.json = JsonPathAssert.this.actual.toString();
- }
-
- @Nullable
- Object assertHasPath() {
- return getValue();
- }
-
- void assertDoesNotHavePath() {
- try {
- read();
- throw failure(new JsonPathNotExpected(this.json, this.path));
- }
- catch (PathNotFoundException ignore) {
- }
- }
-
- @Nullable
- Object getValue() {
- try {
- return read();
- }
- catch (PathNotFoundException ex) {
- throw failure(new JsonPathNotFound(this.json, this.path));
- }
- }
-
- @Nullable
- private Object read() {
- return this.jsonPath.read(this.json);
- }
-
-
- static final class JsonPathNotFound extends BasicErrorMessageFactory {
-
- private JsonPathNotFound(String actual, String path) {
- super("%nExpecting:%n %s%nTo match JSON path:%n %s%n", actual, path);
- }
- }
-
- static final class JsonPathNotExpected extends BasicErrorMessageFactory {
-
- private JsonPathNotExpected(String actual, String path) {
- super("%nExpecting:%n %s%nNot to match JSON path:%n %s%n", actual, path);
- }
- }
- }
-
-}
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssert.java b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssert.java
index ddba8a187f..fe079f7a9d 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssert.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssert.java
@@ -28,7 +28,9 @@ import org.assertj.core.api.Assertions;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatus.Series;
+import org.springframework.http.MediaType;
import org.springframework.test.http.HttpHeadersAssert;
+import org.springframework.test.http.MediaTypeAssert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.function.SingletonSupplier;
@@ -48,15 +50,24 @@ import org.springframework.util.function.SingletonSupplier;
public abstract class AbstractHttpServletResponseAssert, ACTUAL>
extends AbstractObjectAssert {
- private final Supplier> statusAssert;
+ private final Supplier contentTypeAssertSupplier;
private final Supplier headersAssertSupplier;
+ private final Supplier> statusAssert;
+
protected AbstractHttpServletResponseAssert(ACTUAL actual, Class> selfType) {
super(actual, selfType);
- this.statusAssert = SingletonSupplier.of(() -> Assertions.assertThat(getResponse().getStatus()).as("HTTP status code"));
+ this.contentTypeAssertSupplier = SingletonSupplier.of(() -> new MediaTypeAssert(getResponse().getContentType()));
this.headersAssertSupplier = SingletonSupplier.of(() -> new HttpHeadersAssert(getHttpHeaders(getResponse())));
+ this.statusAssert = SingletonSupplier.of(() -> Assertions.assertThat(getResponse().getStatus()).as("HTTP status code"));
+ }
+
+ private static HttpHeaders getHttpHeaders(HttpServletResponse response) {
+ MultiValueMap headers = new LinkedMultiValueMap<>();
+ response.getHeaderNames().forEach(name -> headers.put(name, new ArrayList<>(response.getHeaders(name))));
+ return new HttpHeaders(headers);
}
/**
@@ -67,6 +78,14 @@ public abstract class AbstractHttpServletResponseAssert headers = new LinkedMultiValueMap<>();
- response.getHeaderNames().forEach(name -> headers.put(name, new ArrayList<>(response.getHeaders(name))));
- return new HttpHeaders(headers);
- }
-
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AbstractMockHttpServletResponseAssert.java b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AbstractMockHttpServletResponseAssert.java
index decb5a21e9..c320bea101 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AbstractMockHttpServletResponseAssert.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AbstractMockHttpServletResponseAssert.java
@@ -18,9 +18,16 @@ package org.springframework.test.web.servlet.assertj;
import java.nio.charset.Charset;
+import org.assertj.core.api.AbstractByteArrayAssert;
+import org.assertj.core.api.AbstractStringAssert;
+import org.assertj.core.api.Assertions;
+import org.assertj.core.api.ByteArrayAssert;
+
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.test.json.AbstractJsonContentAssert;
+import org.springframework.test.json.JsonContentAssert;
import org.springframework.test.web.UriAssert;
/**
@@ -45,22 +52,62 @@ public abstract class AbstractMockHttpServletResponseAssertExamples:
* // Check that the response body is equal to "Hello World":
- * assertThat(response).body().isEqualTo("Hello World");
- *
- * // Check that the response body is strictly equal to the content of "test.json":
- * assertThat(response).body().json().isStrictlyEqualToJson("test.json");
+ * assertThat(response).bodyText().isEqualTo("Hello World");
*
*/
- public ResponseBodyAssert body() {
- return new ResponseBodyAssert(getResponse().getContentAsByteArray(),
- Charset.forName(getResponse().getCharacterEncoding()), this.jsonMessageConverter);
+ public AbstractStringAssert> bodyText() {
+ return Assertions.assertThat(readBody());
+ }
+
+ /**
+ * Return a new {@linkplain AbstractJsonContentAssert assertion} object that
+ * uses the response body converted to text as the object to test. Compared
+ * to {@link #bodyText()}, the assertion object provides dedicated JSON
+ * support.
+ * Examples:
+ * // Check that the response body is strictly equal to the content of
+ * // "/com/acme/sample/person-created.json":
+ * assertThat(response).bodyJson()
+ * .isStrictlyEqualToJson("/com/acme/sample/person-created.json");
+ *
+ * // Check that the response is strictly equal to the content of the
+ * // specified file located in the same package as the PersonController:
+ * assertThat(response).bodyJson().withResourceLoadClass(PersonController.class)
+ * .isStrictlyEqualToJson("person-created.json");
+ *
+ * The returned assert object also supports JSON path expressions.
+ * Examples:
+ * // Check that the JSON document does not have an "error" element
+ * assertThat(response).bodyJson().doesNotHavePath("$.error");
+ *
+ * // Check that the JSON document as a top level "message" element
+ * assertThat(response).bodyJson()
+ * .extractingPath("$.message").asString().isEqualTo("hello");
+ *
+ */
+ public AbstractJsonContentAssert> bodyJson() {
+ return new JsonContentAssert(readBody(), this.jsonMessageConverter);
+ }
+
+ private String readBody() {
+ return new String(getResponse().getContentAsByteArray(),
+ Charset.forName(getResponse().getCharacterEncoding()));
+ }
+
+ /**
+ * Return a new {@linkplain AbstractByteArrayAssert assertion} object that
+ * uses the response body as the object to test.
+ * @see #bodyText()
+ * @see #bodyJson()
+ */
+ public AbstractByteArrayAssert> body() {
+ return new ByteArrayAssert(getResponse().getContentAsByteArray());
}
/**
@@ -89,6 +136,14 @@ public abstract class AbstractMockHttpServletResponseAssert jsonMessageConverter;
- DefaultAssertableMvcResult(@Nullable MvcResult target, @Nullable Exception unresolvedException, @Nullable GenericHttpMessageConverter jsonMessageConverter) {
- this.target = target;
+ DefaultMvcTestResult(@Nullable MvcResult mvcResult, @Nullable Exception unresolvedException, @Nullable GenericHttpMessageConverter jsonMessageConverter) {
+ this.mvcResult = mvcResult;
this.unresolvedException = unresolvedException;
this.jsonMessageConverter = jsonMessageConverter;
}
- /**
- * Return the exception that was thrown unexpectedly while processing the
- * request, if any.
- */
+ public MvcResult getMvcResult() {
+ if (this.mvcResult == null) {
+ throw new IllegalStateException(
+ "Request has failed with unresolved exception " + this.unresolvedException);
+ }
+ return this.mvcResult;
+ }
+
@Nullable
public Exception getUnresolvedException() {
return this.unresolvedException;
}
- @Override
public MockHttpServletRequest getRequest() {
- return getTarget().getRequest();
+ return getMvcResult().getRequest();
}
- @Override
public MockHttpServletResponse getResponse() {
- return getTarget().getResponse();
+ return getMvcResult().getResponse();
}
- @Override
- @Nullable
- public Object getHandler() {
- return getTarget().getHandler();
- }
-
- @Override
- @Nullable
- public HandlerInterceptor[] getInterceptors() {
- return getTarget().getInterceptors();
- }
-
- @Override
- @Nullable
- public ModelAndView getModelAndView() {
- return getTarget().getModelAndView();
- }
-
- @Override
@Nullable
public Exception getResolvedException() {
- return getTarget().getResolvedException();
+ return getMvcResult().getResolvedException();
}
- @Override
- public FlashMap getFlashMap() {
- return getTarget().getFlashMap();
- }
-
- @Override
- public Object getAsyncResult() {
- return getTarget().getAsyncResult();
- }
-
- @Override
- public Object getAsyncResult(long timeToWait) {
- return getTarget().getAsyncResult(timeToWait);
- }
-
-
- private MvcResult getTarget() {
- if (this.target == null) {
- throw new IllegalStateException(
- "Request has failed with unresolved exception " + this.unresolvedException);
- }
- return this.target;
- }
/**
* Use AssertJ's {@link org.assertj.core.api.Assertions#assertThat assertThat}
* instead.
*/
@Override
- public MvcResultAssert assertThat() {
- return new MvcResultAssert(this, this.jsonMessageConverter);
+ public MvcTestResultAssert assertThat() {
+ return new MvcTestResultAssert(this, this.jsonMessageConverter);
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AssertableMockMvc.java b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MockMvcTester.java
similarity index 70%
rename from spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AssertableMockMvc.java
rename to spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MockMvcTester.java
index 6e99119b9a..cca3087f60 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AssertableMockMvc.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MockMvcTester.java
@@ -38,15 +38,46 @@ import org.springframework.util.Assert;
import org.springframework.web.context.WebApplicationContext;
/**
- * {@link MockMvc} variant that tests Spring MVC exchanges and provides fluent
- * assertions using {@link org.assertj.core.api.Assertions AssertJ}.
+ * Test Spring MVC applications with {@link MockMvc} for server request handling
+ * using {@link org.assertj.core.api.Assertions AssertJ}.
+ *
+ * A tester instance can be created from a {@link WebApplicationContext}:
+ *
+ * // Create an instance with default settings
+ * MockMvcTester mvc = MockMvcTester.from(applicationContext);
+ *
+ * // Create an instance with a custom Filter
+ * MockMvcTester mvc = MockMvcTester.from(applicationContext,
+ * builder -> builder.addFilters(filter).build());
+ *
+ *
+ * A tester can be created standalone by providing the controller(s) to
+ * include in a standalone setup:
+ * // Create an instance for PersonController
+ * MockMvcTester mvc = MockMvcTester.of(new PersonController());
+ *
+ *
+ * Once a test instance is available, you can perform requests in
+ * a similar fashion as with {@link MockMvc}, and wrapping the result in
+ * {@code assertThat} provides access to assertions. For instance:
+ *
+ * // perform a GET on /hi and assert the response body is equal to Hello
+ * assertThat(mvc.perform(get("/hi")))
+ * .hasStatusOk().hasBodyTextEqualTo("Hello");
+ *
*
* A main difference with {@link MockMvc} is that an unresolved exception
- * is not thrown directly. Rather an {@link AssertableMvcResult} is available
- * with an {@link AssertableMvcResult#getUnresolvedException() unresolved
- * exception}.
+ * is not thrown directly. Rather an {@link MvcTestResult} is available
+ * with an {@link MvcTestResult#getUnresolvedException() unresolved
+ * exception}. You can assert that a request has failed unexpectedly:
+ *
+ * // perform a GET on /hi and assert the response body is equal to Hello
+ * assertThat(mvc.perform(get("/boom")))
+ * .hasUnresolvedException())
+ * .withMessage("Test exception");
+ *
*
- * {@link AssertableMockMvc} can be configured with a list of
+ *
{@link MockMvcTester} can be configured with a list of
* {@linkplain HttpMessageConverter message converters} to allow the response
* body to be deserialized, rather than asserting on the raw values.
*
@@ -54,7 +85,7 @@ import org.springframework.web.context.WebApplicationContext;
* @author Brian Clozel
* @since 6.2
*/
-public final class AssertableMockMvc {
+public final class MockMvcTester {
private static final MediaType JSON = MediaType.APPLICATION_JSON;
@@ -64,23 +95,23 @@ public final class AssertableMockMvc {
private final GenericHttpMessageConverter jsonMessageConverter;
- private AssertableMockMvc(MockMvc mockMvc, @Nullable GenericHttpMessageConverter jsonMessageConverter) {
+ private MockMvcTester(MockMvc mockMvc, @Nullable GenericHttpMessageConverter jsonMessageConverter) {
Assert.notNull(mockMvc, "mockMVC should not be null");
this.mockMvc = mockMvc;
this.jsonMessageConverter = jsonMessageConverter;
}
/**
- * Create a {@link AssertableMockMvc} instance that delegates to the given
+ * Create a {@link MockMvcTester} instance that delegates to the given
* {@link MockMvc} instance.
* @param mockMvc the MockMvc instance to delegate calls to
*/
- public static AssertableMockMvc create(MockMvc mockMvc) {
- return new AssertableMockMvc(mockMvc, null);
+ public static MockMvcTester create(MockMvc mockMvc) {
+ return new MockMvcTester(mockMvc, null);
}
/**
- * Create an {@link AssertableMockMvc} instance using the given, fully
+ * Create an {@link MockMvcTester} instance using the given, fully
* initialized (i.e., refreshed ) {@link WebApplicationContext}. The
* given {@code customizations} are applied to the {@link DefaultMockMvcBuilder}
* that ultimately creates the underlying {@link MockMvc} instance.
@@ -92,7 +123,7 @@ public final class AssertableMockMvc {
* instance based on a {@link DefaultMockMvcBuilder}.
* @see MockMvcBuilders#webAppContextSetup(WebApplicationContext)
*/
- public static AssertableMockMvc from(WebApplicationContext applicationContext,
+ public static MockMvcTester from(WebApplicationContext applicationContext,
Function customizations) {
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(applicationContext);
@@ -101,7 +132,7 @@ public final class AssertableMockMvc {
}
/**
- * Shortcut to create an {@link AssertableMockMvc} instance using the given,
+ * Shortcut to create an {@link MockMvcTester} instance using the given,
* fully initialized (i.e., refreshed ) {@link WebApplicationContext}.
* Consider using {@link #from(WebApplicationContext, Function)} if
* further customization of the underlying {@link MockMvc} instance is
@@ -110,12 +141,12 @@ public final class AssertableMockMvc {
* MVC infrastructure and application controllers from
* @see MockMvcBuilders#webAppContextSetup(WebApplicationContext)
*/
- public static AssertableMockMvc from(WebApplicationContext applicationContext) {
+ public static MockMvcTester from(WebApplicationContext applicationContext) {
return from(applicationContext, DefaultMockMvcBuilder::build);
}
/**
- * Create an {@link AssertableMockMvc} instance by registering one or more
+ * Create an {@link MockMvcTester} instance by registering one or more
* {@code @Controller} instances and configuring Spring MVC infrastructure
* programmatically.
*
This allows full control over the instantiation and initialization of
@@ -129,7 +160,7 @@ public final class AssertableMockMvc {
* Spring MVC infrastructure
* @see MockMvcBuilders#standaloneSetup(Object...)
*/
- public static AssertableMockMvc of(Collection> controllers,
+ public static MockMvcTester of(Collection> controllers,
Function customizations) {
StandaloneMockMvcBuilder builder = MockMvcBuilders.standaloneSetup(controllers.toArray());
@@ -137,8 +168,8 @@ public final class AssertableMockMvc {
}
/**
- * Shortcut to create an {@link AssertableMockMvc} instance by registering
- * one or more {@code @Controller} instances.
+ * Shortcut to create an {@link MockMvcTester} instance by registering one
+ * or more {@code @Controller} instances.
* The minimum infrastructure required by the
* {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet}
* to serve requests with annotated controllers is created. Consider using
@@ -149,12 +180,12 @@ public final class AssertableMockMvc {
* into an instance
* @see MockMvcBuilders#standaloneSetup(Object...)
*/
- public static AssertableMockMvc of(Object... controllers) {
+ public static MockMvcTester of(Object... controllers) {
return of(Arrays.asList(controllers), StandaloneMockMvcBuilder::build);
}
/**
- * Return a new {@link AssertableMockMvc} instance using the specified
+ * Return a new {@link MockMvcTester} instance using the specified
* {@linkplain HttpMessageConverter message converters}.
*
If none are specified, only basic assertions on the response body can
* be performed. Consider registering a suitable JSON converter for asserting
@@ -162,13 +193,13 @@ public final class AssertableMockMvc {
* @param httpMessageConverters the message converters to use
* @return a new instance using the specified converters
*/
- public AssertableMockMvc withHttpMessageConverters(Iterable> httpMessageConverters) {
- return new AssertableMockMvc(this.mockMvc, findJsonMessageConverter(httpMessageConverters));
+ public MockMvcTester withHttpMessageConverters(Iterable> httpMessageConverters) {
+ return new MockMvcTester(this.mockMvc, findJsonMessageConverter(httpMessageConverters));
}
/**
- * Perform a request and return a type that can be used with standard
- * {@link org.assertj.core.api.Assertions AssertJ} assertions.
+ * Perform a request and return a {@link MvcTestResult result} that can be
+ * used with standard {@link org.assertj.core.api.Assertions AssertJ} assertions.
* Use static methods of {@link MockMvcRequestBuilders} to prepare the
* request, wrapping the invocation in {@code assertThat}. The following
* asserts that a {@linkplain MockMvcRequestBuilders#get(URI) GET} request
@@ -191,16 +222,16 @@ public final class AssertableMockMvc {
* @param requestBuilder used to prepare the request to execute;
* see static factory methods in
* {@link org.springframework.test.web.servlet.request.MockMvcRequestBuilders}
- * @return an {@link AssertableMvcResult} to be wrapped in {@code assertThat}
+ * @return an {@link MvcTestResult} to be wrapped in {@code assertThat}
* @see MockMvc#perform(RequestBuilder)
*/
- public AssertableMvcResult perform(RequestBuilder requestBuilder) {
+ public MvcTestResult perform(RequestBuilder requestBuilder) {
Object result = getMvcResultOrFailure(requestBuilder);
if (result instanceof MvcResult mvcResult) {
- return new DefaultAssertableMvcResult(mvcResult, null, this.jsonMessageConverter);
+ return new DefaultMvcTestResult(mvcResult, null, this.jsonMessageConverter);
}
else {
- return new DefaultAssertableMvcResult(null, (Exception) result, this.jsonMessageConverter);
+ return new DefaultMvcTestResult(null, (Exception) result, this.jsonMessageConverter);
}
}
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AssertableMvcResult.java b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResult.java
similarity index 61%
rename from spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AssertableMvcResult.java
rename to spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResult.java
index 8f0674db61..db8745bc17 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/AssertableMvcResult.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResult.java
@@ -22,23 +22,34 @@ import org.springframework.lang.Nullable;
import org.springframework.test.web.servlet.MvcResult;
/**
- * A {@link MvcResult} that additionally supports AssertJ style assertions.
+ * Provide to the result of an executed request using {@link MockMvcTester} that
+ * is meant to be used with {@link org.assertj.core.api.Assertions#assertThat(AssertProvider)
+ * assertThat}.
*
*
Can be in one of two distinct states:
*
- * The request processed successfully, and {@link #getUnresolvedException()}
- * is therefore {@code null}.
+ * The request processed successfully, even if it fails with an exception
+ * that has been resolved. {@link #getMvcResult()} is available and
+ * {@link #getUnresolvedException()} is {@code null}.
* The request failed unexpectedly with {@link #getUnresolvedException()}
- * providing more information about the error. Any attempt to access a member of
- * the result fails with an exception.
+ * providing more information about the error. Any attempt to access
+ * {@link #getMvcResult() the result } fails with an exception.
*
*
* @author Stephane Nicoll
* @author Brian Clozel
* @since 6.2
- * @see AssertableMockMvc
+ * @see MockMvcTester
*/
-public interface AssertableMvcResult extends MvcResult, AssertProvider {
+public interface MvcTestResult extends AssertProvider {
+
+ /**
+ * Return the {@link MvcResult result} of the processing.
+ * If the request has failed unexpectedly, this throws an
+ * {@link IllegalStateException}.
+ * @return the {@link MvcResult}
+ */
+ MvcResult getMvcResult();
/**
* Return the exception that was thrown unexpectedly while processing the
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcResultAssert.java b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResultAssert.java
similarity index 78%
rename from spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcResultAssert.java
rename to spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResultAssert.java
index e4e666546f..7c7ff332ec 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcResultAssert.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResultAssert.java
@@ -30,12 +30,10 @@ import org.assertj.core.api.ObjectAssert;
import org.assertj.core.error.BasicErrorMessageFactory;
import org.assertj.core.internal.Failures;
-import org.springframework.http.MediaType;
import org.springframework.http.converter.GenericHttpMessageConverter;
import org.springframework.lang.Nullable;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.mock.web.MockHttpServletResponse;
-import org.springframework.test.http.MediaTypeAssert;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultHandler;
import org.springframework.test.web.servlet.ResultMatcher;
@@ -43,22 +41,22 @@ import org.springframework.web.servlet.ModelAndView;
/**
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied
- * to {@link MvcResult}.
+ * to {@link MvcTestResult}.
*
* @author Stephane Nicoll
* @author Brian Clozel
* @since 6.2
*/
-public class MvcResultAssert extends AbstractMockHttpServletResponseAssert {
+public class MvcTestResultAssert extends AbstractMockHttpServletResponseAssert {
- MvcResultAssert(AssertableMvcResult mvcResult, @Nullable GenericHttpMessageConverter jsonMessageConverter) {
- super(jsonMessageConverter, mvcResult, MvcResultAssert.class);
+ MvcTestResultAssert(MvcTestResult actual, @Nullable GenericHttpMessageConverter jsonMessageConverter) {
+ super(jsonMessageConverter, actual, MvcTestResultAssert.class);
}
@Override
protected MockHttpServletResponse getResponse() {
- checkHasNotFailedUnexpectedly();
- return this.actual.getResponse();
+ getMvcResult();
+ return this.actual.getMvcResult().getResponse();
}
/**
@@ -76,8 +74,7 @@ public class MvcResultAssert extends AbstractMockHttpServletResponseAssert request() {
- checkHasNotFailedUnexpectedly();
- return new MockHttpRequestAssert(this.actual.getRequest());
+ return new MockHttpRequestAssert(getMvcResult().getRequest());
}
/**
@@ -85,17 +82,7 @@ public class MvcResultAssert extends AbstractMockHttpServletResponseAssert
*/
public HandlerResultAssert handler() {
- checkHasNotFailedUnexpectedly();
- return new HandlerResultAssert(this.actual.getHandler());
+ return new HandlerResultAssert(getMvcResult().getHandler());
}
/**
@@ -118,7 +104,6 @@ public class MvcResultAssert extends AbstractMockHttpServletResponseAssert viewName() {
- checkHasNotFailedUnexpectedly();
return Assertions.assertThat(getModelAndView().getViewName()).as("View name");
}
@@ -139,8 +123,7 @@ public class MvcResultAssert extends AbstractMockHttpServletResponseAssert flash() {
- checkHasNotFailedUnexpectedly();
- return new MapAssert<>(this.actual.getFlashMap());
+ return new MapAssert<>(getMvcResult().getFlashMap());
}
/**
@@ -151,14 +134,14 @@ public class MvcResultAssert extends AbstractMockHttpServletResponseAssert asyncResult() {
request().hasAsyncStarted(true);
- return Assertions.assertThat(this.actual.getAsyncResult()).as("Async result");
+ return Assertions.assertThat(getMvcResult().getAsyncResult()).as("Async result");
}
/**
* Verify that the request has failed with an unresolved exception.
* @see #unresolvedException()
*/
- public MvcResultAssert hasUnresolvedException() {
+ public MvcTestResultAssert hasUnresolvedException() {
Assertions.assertThat(this.actual.getUnresolvedException())
.withFailMessage("Expecting request to have failed but it has succeeded").isNotNull();
return this;
@@ -167,7 +150,7 @@ public class MvcResultAssert extends AbstractMockHttpServletResponseAssert resultMatcher.match(mvcResult));
}
/**
* Apply the given {@link ResultHandler} to the actual mvc result.
* @param resultHandler the result matcher to invoke
*/
- public MvcResultAssert apply(ResultHandler resultHandler) {
- checkHasNotFailedUnexpectedly();
- return satisfies(resultHandler::handle);
+ public MvcTestResultAssert apply(ResultHandler resultHandler) {
+ MvcResult mvcResult = getMvcResult();
+ return satisfies(tmc -> resultHandler.handle(mvcResult));
}
/**
@@ -197,7 +180,7 @@ public class MvcResultAssert extends AbstractMockHttpServletResponseAssert {
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/ResponseBodyAssert.java b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/ResponseBodyAssert.java
deleted file mode 100644
index fffd483150..0000000000
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/ResponseBodyAssert.java
+++ /dev/null
@@ -1,128 +0,0 @@
-/*
- * Copyright 2002-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.test.web.servlet.assertj;
-
-import java.nio.charset.Charset;
-
-import jakarta.servlet.http.HttpServletResponse;
-import org.assertj.core.api.AbstractByteArrayAssert;
-import org.assertj.core.api.AbstractStringAssert;
-
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.http.converter.GenericHttpMessageConverter;
-import org.springframework.lang.Nullable;
-import org.springframework.test.json.JsonContentAssert;
-import org.springframework.test.json.JsonPathAssert;
-
-/**
- * AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to
- * the response body.
- *
- * @author Stephane Nicoll
- * @author Brian Clozel
- * @since 6.2
- */
-public class ResponseBodyAssert extends AbstractByteArrayAssert {
-
- private final Charset characterEncoding;
-
- @Nullable
- private final GenericHttpMessageConverter jsonMessageConverter;
-
- ResponseBodyAssert(byte[] actual, Charset characterEncoding,
- @Nullable GenericHttpMessageConverter jsonMessageConverter) {
-
- super(actual, ResponseBodyAssert.class);
- this.characterEncoding = characterEncoding;
- this.jsonMessageConverter = jsonMessageConverter;
- as("Response body");
- }
-
- /**
- * Return a new {@linkplain JsonPathAssert assertion} object that provides
- * {@linkplain com.jayway.jsonpath.JsonPath JSON path} assertions on the
- * response body.
- */
- public JsonPathAssert jsonPath() {
- return new JsonPathAssert(getJson(), this.jsonMessageConverter);
- }
-
- /**
- * Return a new {@linkplain JsonContentAssert assertion} object that provides
- * support for {@linkplain org.skyscreamer.jsonassert.JSONCompareMode JSON
- * assert} comparisons against expected JSON input which can be loaded from
- * the classpath.
- * This method only supports absolute locations for JSON documents loaded
- * from the classpath. Consider using {@link #json(Class)} to load JSON
- * documents relative to a given class.
- *
Example:
- * // Check that the response is strictly equal to the content of
- * // "/com/acme/web/person/person-created.json":
- * assertThat(...).body().json()
- * .isStrictlyEqualToJson("/com/acme/web/person/person-created.json");
- *
- */
- public JsonContentAssert json() {
- return json(null);
- }
-
- /**
- * Return a new {@linkplain JsonContentAssert assertion} object that provides
- * support for {@linkplain org.skyscreamer.jsonassert.JSONCompareMode JSON
- * assert} comparisons against expected JSON input which can be loaded from
- * the classpath.
- * Locations for JSON documents can be absolute using a leading slash, or
- * relative to the given {@code resourceLoadClass}.
- *
Example:
- * // Check that the response is strictly equal to the content of the
- * // specified file located in the same package as the PersonController:
- * assertThat(...).body().json(PersonController.class)
- * .isStrictlyEqualToJson("person-created.json");
- *
- * @param resourceLoadClass the class used to load relative JSON documents
- * @see ClassPathResource#ClassPathResource(String, Class)
- */
- public JsonContentAssert json(@Nullable Class> resourceLoadClass) {
- return new JsonContentAssert(getJson(), resourceLoadClass, this.characterEncoding);
- }
-
- /**
- * Verify that the response body is equal to the given {@link String}.
- * Converts the actual byte array to a String using the character encoding
- * of the {@link HttpServletResponse}.
- * @param expected the expected content of the response body
- * @see #asString()
- */
- public ResponseBodyAssert isEqualTo(String expected) {
- asString().isEqualTo(expected);
- return this;
- }
-
- /**
- * Override that uses the character encoding of the {@link HttpServletResponse}
- * to convert the byte[] to a String, rather than the platform's default charset.
- */
- @Override
- public AbstractStringAssert> asString() {
- return asString(this.characterEncoding);
- }
-
- private String getJson() {
- return new String(this.actual, this.characterEncoding);
- }
-
-}
diff --git a/spring-test/src/test/java/org/springframework/test/json/AbstractJsonContentAssertTests.java b/spring-test/src/test/java/org/springframework/test/json/AbstractJsonContentAssertTests.java
new file mode 100644
index 0000000000..14f1ad8dcf
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/json/AbstractJsonContentAssertTests.java
@@ -0,0 +1,785 @@
+/*
+ * Copyright 2002-2024 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * https://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.springframework.test.json;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.stream.Stream;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import org.assertj.core.api.AssertProvider;
+import org.junit.jupiter.api.Nested;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.api.TestInstance;
+import org.junit.jupiter.api.TestInstance.Lifecycle;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import org.skyscreamer.jsonassert.JSONCompareMode;
+import org.skyscreamer.jsonassert.comparator.DefaultComparator;
+import org.skyscreamer.jsonassert.comparator.JSONComparator;
+
+import org.springframework.core.ParameterizedTypeReference;
+import org.springframework.core.io.ByteArrayResource;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.core.io.FileSystemResource;
+import org.springframework.core.io.InputStreamResource;
+import org.springframework.core.io.Resource;
+import org.springframework.http.converter.GenericHttpMessageConverter;
+import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
+import org.springframework.lang.Nullable;
+import org.springframework.util.FileCopyUtils;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
+import static org.assertj.core.api.Assertions.entry;
+
+/**
+ * Tests for {@link AbstractJsonContentAssert}.
+ *
+ * @author Stephane Nicoll
+ * @author Phillip Webb
+ */
+class AbstractJsonContentAssertTests {
+
+ private static final String TYPES = loadJson("types.json");
+
+ private static final String SIMPSONS = loadJson("simpsons.json");
+
+ private static final String NULLS = loadJson("nulls.json");
+
+ private static final String SOURCE = loadJson("source.json");
+
+ private static final String LENIENT_SAME = loadJson("lenient-same.json");
+
+ private static final String DIFFERENT = loadJson("different.json");
+
+ private static final MappingJackson2HttpMessageConverter jsonHttpMessageConverter =
+ new MappingJackson2HttpMessageConverter(new ObjectMapper());
+
+ private static final JSONComparator comparator = new DefaultComparator(JSONCompareMode.LENIENT);
+
+ @Test
+ void isNullWhenActualIsNullShouldPass() {
+ assertThat(forJson(null)).isNull();
+ }
+
+ @Nested
+ class HasPathTests {
+
+ @Test
+ void hasPathForNullJson() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(null)).hasPath("no"))
+ .withMessageContaining("Expecting actual not to be null");
+ }
+
+ @Test
+ void hasPathForPresentAndNotNull() {
+ assertThat(forJson(NULLS)).hasPath("$.valuename");
+ }
+
+ @Test
+ void hasPathForPresentAndNull() {
+ assertThat(forJson(NULLS)).hasPath("$.nullname");
+ }
+
+ @Test
+ void hasPathForOperatorMatching() {
+ assertThat(forJson(SIMPSONS)).
+ hasPath("$.familyMembers[?(@.name == 'Homer')]");
+ }
+
+ @Test
+ void hasPathForOperatorNotMatching() {
+ assertThat(forJson(SIMPSONS)).
+ hasPath("$.familyMembers[?(@.name == 'Dilbert')]");
+ }
+
+ @Test
+ void hasPathForNotPresent() {
+ String expression = "$.missing";
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(NULLS)).hasPath(expression))
+ .satisfies(hasFailedToMatchPath("$.missing"));
+ }
+
+ @Test
+ void hasPathSatisfying() {
+ assertThat(forJson(TYPES)).hasPathSatisfying("$.str", value -> assertThat(value).isEqualTo("foo"))
+ .hasPathSatisfying("$.num", value -> assertThat(value).isEqualTo(5));
+ }
+
+ @Test
+ void hasPathSatisfyingForPathNotPresent() {
+ String expression = "missing";
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(NULLS)).hasPathSatisfying(expression, value -> {}))
+ .satisfies(hasFailedToMatchPath(expression));
+ }
+
+ @Test
+ void doesNotHavePathForMissing() {
+ assertThat(forJson(NULLS)).doesNotHavePath("$.missing");
+ }
+
+ @Test
+ void doesNotHavePathForPresent() {
+ String expression = "$.valuename";
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(NULLS)).doesNotHavePath(expression))
+ .satisfies(hasFailedToNotMatchPath(expression));
+ }
+ }
+
+ @Nested
+ class ExtractingPathTests {
+
+ @Test
+ void extractingPathForNullJson() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(null)).extractingPath("$"))
+ .withMessageContaining("Expecting actual not to be null");
+ }
+
+ @Test
+ void isNullWithNullPathValue() {
+ assertThat(forJson(NULLS)).extractingPath("$.nullname").isNull();
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "$.str", "$.emptyString", "$.num", "$.bool", "$.arr",
+ "$.emptyArray", "$.colorMap", "$.emptyMap" })
+ void isNotNullWithValue(String path) {
+ assertThat(forJson(TYPES)).extractingPath(path).isNotNull();
+ }
+
+ @ParameterizedTest
+ @MethodSource
+ void isEqualToOnRawValue(String path, Object expected) {
+ assertThat(forJson(TYPES)).extractingPath(path).isEqualTo(expected);
+ }
+
+ static Stream isEqualToOnRawValue() {
+ return Stream.of(
+ Arguments.of("$.str", "foo"),
+ Arguments.of("$.num", 5),
+ Arguments.of("$.bool", true),
+ Arguments.of("$.arr", List.of(42)),
+ Arguments.of("$.colorMap", Map.of("red", "rojo")));
+ }
+
+ @Test
+ void asStringWithActualValue() {
+ assertThat(forJson(TYPES)).extractingPath("@.str").asString().startsWith("f").endsWith("o");
+ }
+
+ @Test
+ void asStringIsEmpty() {
+ assertThat(forJson(TYPES)).extractingPath("@.emptyString").asString().isEmpty();
+ }
+
+ @Test
+ void asNumberWithActualValue() {
+ assertThat(forJson(TYPES)).extractingPath("@.num").asNumber().isEqualTo(5);
+ }
+
+ @Test
+ void asBooleanWithActualValue() {
+ assertThat(forJson(TYPES)).extractingPath("@.bool").asBoolean().isTrue();
+ }
+
+ @Test
+ void asArrayWithActualValue() {
+ assertThat(forJson(TYPES)).extractingPath("@.arr").asArray().containsOnly(42);
+ }
+
+ @Test
+ void asArrayIsEmpty() {
+ assertThat(forJson(TYPES)).extractingPath("@.emptyArray").asArray().isEmpty();
+ }
+
+ @Test
+ void asArrayWithFilterPredicatesMatching() {
+ assertThat(forJson(SIMPSONS))
+ .extractingPath("$.familyMembers[?(@.name == 'Bart')]").asArray().hasSize(1);
+ }
+
+ @Test
+ void asArrayWithFilterPredicatesNotMatching() {
+ assertThat(forJson(SIMPSONS)).
+ extractingPath("$.familyMembers[?(@.name == 'Dilbert')]").asArray().isEmpty();
+ }
+
+ @Test
+ void asMapWithActualValue() {
+ assertThat(forJson(TYPES)).extractingPath("@.colorMap").asMap().containsOnly(entry("red", "rojo"));
+ }
+
+ @Test
+ void asMapIsEmpty() {
+ assertThat(forJson(TYPES)).extractingPath("@.emptyMap").asMap().isEmpty();
+ }
+
+ @Test
+ void convertToWithoutHttpMessageConverterShouldFail() {
+ JsonPathValueAssert path = assertThat(forJson(SIMPSONS)).extractingPath("$.familyMembers[0]");
+ assertThatIllegalStateException()
+ .isThrownBy(() -> path.convertTo(ExtractingPathTests.Member.class))
+ .withMessage("No JSON message converter available to convert {name=Homer}");
+ }
+
+ @Test
+ void convertToTargetType() {
+ assertThat(forJson(SIMPSONS, jsonHttpMessageConverter))
+ .extractingPath("$.familyMembers[0]").convertTo(ExtractingPathTests.Member.class)
+ .satisfies(member -> assertThat(member.name).isEqualTo("Homer"));
+ }
+
+ @Test
+ void convertToIncompatibleTargetTypeShouldFail() {
+ JsonPathValueAssert path = assertThat(forJson(SIMPSONS, jsonHttpMessageConverter))
+ .extractingPath("$.familyMembers[0]");
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> path.convertTo(ExtractingPathTests.Customer.class))
+ .withMessageContainingAll("Expected value at JSON path \"$.familyMembers[0]\":",
+ Customer.class.getName(), "name");
+ }
+
+ @Test
+ void convertArrayToParameterizedType() {
+ assertThat(forJson(SIMPSONS, jsonHttpMessageConverter))
+ .extractingPath("$.familyMembers")
+ .convertTo(new ParameterizedTypeReference>() {})
+ .satisfies(family -> assertThat(family).hasSize(5).element(0).isEqualTo(new Member("Homer")));
+ }
+
+ @Test
+ void isEmptyWithPathHavingNullValue() {
+ assertThat(forJson(NULLS)).extractingPath("nullname").isEmpty();
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "$.emptyString", "$.emptyArray", "$.emptyMap" })
+ void isEmptyWithEmptyValue(String path) {
+ assertThat(forJson(TYPES)).extractingPath(path).isEmpty();
+ }
+
+ @Test
+ void isEmptyForPathWithFilterMatching() {
+ String expression = "$.familyMembers[?(@.name == 'Bart')]";
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SIMPSONS)).extractingPath(expression).isEmpty())
+ .withMessageContainingAll("Expected value at JSON path \"" + expression + "\"",
+ "[{\"name\":\"Bart\"}]", "To be empty");
+ }
+
+ @Test
+ void isEmptyForPathWithFilterNotMatching() {
+ assertThat(forJson(SIMPSONS)).extractingPath("$.familyMembers[?(@.name == 'Dilbert')]").isEmpty();
+ }
+
+ @ParameterizedTest
+ @ValueSource(strings = { "$.str", "$.num", "$.bool", "$.arr", "$.colorMap" })
+ void isNotEmptyWithNonNullValue(String path) {
+ assertThat(forJson(TYPES)).extractingPath(path).isNotEmpty();
+ }
+
+ @Test
+ void isNotEmptyForPathWithFilterMatching() {
+ assertThat(forJson(SIMPSONS)).extractingPath("$.familyMembers[?(@.name == 'Bart')]").isNotEmpty();
+ }
+
+ @Test
+ void isNotEmptyForPathWithFilterNotMatching() {
+ String expression = "$.familyMembers[?(@.name == 'Dilbert')]";
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SIMPSONS)).extractingPath(expression).isNotEmpty())
+ .withMessageContainingAll("Expected value at JSON path \"" + expression + "\"",
+ "To not be empty");
+ }
+
+
+ private record Member(String name) {}
+
+ private record Customer(long id, String username) {}
+
+ private AssertProvider> forJson(@Nullable String json) {
+ return () -> new TestJsonContentAssert(json, null);
+ }
+
+ private AssertProvider> forJson(@Nullable String json, GenericHttpMessageConverter jsonHttpMessageConverter) {
+ return () -> new TestJsonContentAssert(json, jsonHttpMessageConverter);
+ }
+ }
+
+ @Nested
+ class EqualsNotEqualsTests {
+
+ @Test
+ void isEqualToWhenStringIsMatchingShouldPass() {
+ assertThat(forJson(SOURCE)).isEqualTo(SOURCE);
+ }
+
+ @Test
+ void isEqualToWhenNullActualShouldFail() {
+ assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
+ assertThat(forJson(null)).isEqualTo(SOURCE));
+ }
+
+ @Test
+ void isEqualToWhenExpectedIsNotAStringShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo(SOURCE.getBytes()));
+ }
+ }
+
+ @Nested
+ @TestInstance(Lifecycle.PER_CLASS)
+ class JsonAssertTests {
+
+ @Test
+ void isEqualToWhenExpectedIsNullShouldFail() {
+ CharSequence actual = null;
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo(actual, JSONCompareMode.LENIENT));
+ }
+
+ @Test
+ void isEqualToWhenStringIsMatchingAndLenientShouldPass() {
+ assertThat(forJson(SOURCE)).isEqualTo(LENIENT_SAME, JSONCompareMode.LENIENT);
+ }
+
+ @Test
+ void isEqualToWhenStringIsNotMatchingAndLenientShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo(DIFFERENT, JSONCompareMode.LENIENT));
+ }
+
+ @Test
+ void isEqualToWhenResourcePathIsMatchingAndLenientShouldPass() {
+ assertThat(forJson(SOURCE)).isEqualTo("lenient-same.json", JSONCompareMode.LENIENT);
+ }
+
+ @Test
+ void isEqualToWhenResourcePathIsNotMatchingAndLenientShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo("different.json", JSONCompareMode.LENIENT));
+ }
+
+ Stream source() {
+ return Stream.of(
+ Arguments.of(new ClassPathResource("source.json", AbstractJsonContentAssertTests.class)),
+ Arguments.of(new ByteArrayResource(SOURCE.getBytes())),
+ Arguments.of(new FileSystemResource(createFile(SOURCE))),
+ Arguments.of(new InputStreamResource(createInputStream(SOURCE))));
+ }
+
+ Stream lenientSame() {
+ return Stream.of(
+ Arguments.of(new ClassPathResource("lenient-same.json", AbstractJsonContentAssertTests.class)),
+ Arguments.of(new ByteArrayResource(LENIENT_SAME.getBytes())),
+ Arguments.of(new FileSystemResource(createFile(LENIENT_SAME))),
+ Arguments.of(new InputStreamResource(createInputStream(LENIENT_SAME))));
+ }
+
+ Stream different() {
+ return Stream.of(
+ Arguments.of(new ClassPathResource("different.json", AbstractJsonContentAssertTests.class)),
+ Arguments.of(new ByteArrayResource(DIFFERENT.getBytes())),
+ Arguments.of(new FileSystemResource(createFile(DIFFERENT))),
+ Arguments.of(new InputStreamResource(createInputStream(DIFFERENT))));
+ }
+
+ @ParameterizedTest
+ @MethodSource("lenientSame")
+ void isEqualToWhenResourceIsMatchingAndLenientSameShouldPass(Resource expected) {
+ assertThat(forJson(SOURCE)).isEqualTo(expected, JSONCompareMode.LENIENT);
+ }
+
+ @ParameterizedTest
+ @MethodSource("different")
+ void isEqualToWhenResourceIsNotMatchingAndLenientShouldFail(Resource expected) {
+ assertThatExceptionOfType(AssertionError.class).isThrownBy(
+ () -> assertThat(forJson(SOURCE)).isEqualTo(expected, JSONCompareMode.LENIENT));
+ }
+
+ @Test
+ void isEqualToWhenStringIsMatchingAndComparatorShouldPass() {
+ assertThat(forJson(SOURCE)).isEqualTo(LENIENT_SAME, comparator);
+ }
+
+ @Test
+ void isEqualToWhenStringIsNotMatchingAndComparatorShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo(DIFFERENT, comparator));
+ }
+
+ @Test
+ void isEqualToWhenResourcePathIsMatchingAndComparatorShouldPass() {
+ assertThat(forJson(SOURCE)).isEqualTo("lenient-same.json", comparator);
+ }
+
+ @Test
+ void isEqualToWhenResourcePathIsNotMatchingAndComparatorShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo("different.json", comparator));
+ }
+
+ @ParameterizedTest
+ @MethodSource("lenientSame")
+ void isEqualToWhenResourceIsMatchingAndComparatorShouldPass(Resource expected) {
+ assertThat(forJson(SOURCE)).isEqualTo(expected, comparator);
+ }
+
+ @ParameterizedTest
+ @MethodSource("different")
+ void isEqualToWhenResourceIsNotMatchingAndComparatorShouldFail(Resource expected) {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo(expected, comparator));
+ }
+
+ @Test
+ void isLenientlyEqualToWhenStringIsMatchingShouldPass() {
+ assertThat(forJson(SOURCE)).isLenientlyEqualTo(LENIENT_SAME);
+ }
+
+ @Test
+ void isLenientlyEqualToWhenNullActualShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(null)).isLenientlyEqualTo(SOURCE));
+ }
+
+ @Test
+ void isLenientlyEqualToWhenStringIsNotMatchingShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isLenientlyEqualTo(DIFFERENT));
+ }
+
+ @Test
+ void isLenientlyEqualToWhenExpectedDoesNotExistShouldFail() {
+ assertThatIllegalStateException()
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isLenientlyEqualTo("does-not-exist.json"))
+ .withMessage("Unable to load JSON from class path resource [org/springframework/test/json/does-not-exist.json]");
+ }
+
+ @Test
+ void isLenientlyEqualToWhenResourcePathIsMatchingShouldPass() {
+ assertThat(forJson(SOURCE)).isLenientlyEqualTo("lenient-same.json");
+ }
+
+ @Test
+ void isLenientlyEqualToWhenResourcePathIsNotMatchingShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isLenientlyEqualTo("different.json"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("lenientSame")
+ void isLenientlyEqualToWhenResourceIsMatchingShouldPass(Resource expected) {
+ assertThat(forJson(SOURCE)).isLenientlyEqualTo(expected);
+ }
+
+ @ParameterizedTest
+ @MethodSource("different")
+ void isLenientlyEqualToWhenResourceIsNotMatchingShouldFail(Resource expected) {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isLenientlyEqualTo(expected));
+ }
+
+ @Test
+ void isStrictlyEqualToWhenStringIsMatchingShouldPass() {
+ assertThat(forJson(SOURCE)).isStrictlyEqualTo(SOURCE);
+ }
+
+ @Test
+ void isStrictlyEqualToWhenStringIsNotMatchingShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isStrictlyEqualTo(LENIENT_SAME));
+ }
+
+ @Test
+ void isStrictlyEqualToWhenResourcePathIsMatchingShouldPass() {
+ assertThat(forJson(SOURCE)).isStrictlyEqualTo("source.json");
+ }
+
+ @Test
+ void isStrictlyEqualToWhenResourcePathIsNotMatchingShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isStrictlyEqualTo("lenient-same.json"));
+ }
+
+ @ParameterizedTest
+ @MethodSource("source")
+ void isStrictlyEqualToWhenResourceIsMatchingShouldPass(Resource expected) {
+ assertThat(forJson(SOURCE)).isStrictlyEqualTo(expected);
+ }
+
+ @ParameterizedTest
+ @MethodSource("lenientSame")
+ void isStrictlyEqualToWhenResourceIsNotMatchingShouldFail(Resource expected) {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isStrictlyEqualTo(expected));
+ }
+
+ @Test
+ void isNotEqualToWhenStringIsMatchingShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotEqualTo(SOURCE));
+ }
+
+ @Test
+ void isNotEqualToWhenNullActualShouldPass() {
+ assertThat(forJson(null)).isNotEqualTo(SOURCE);
+ }
+
+ @Test
+ void isNotEqualToWhenStringIsNotMatchingShouldPass() {
+ assertThat(forJson(SOURCE)).isNotEqualTo(DIFFERENT);
+ }
+
+ @Test
+ void isNotEqualToAsObjectWhenExpectedIsNotAStringShouldNotFail() {
+ assertThat(forJson(SOURCE)).isNotEqualTo(SOURCE.getBytes());
+ }
+
+ @Test
+ void isNotEqualToWhenStringIsMatchingAndLenientShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotEqualTo(LENIENT_SAME, JSONCompareMode.LENIENT));
+ }
+
+ @Test
+ void isNotEqualToWhenStringIsNotMatchingAndLenientShouldPass() {
+ assertThat(forJson(SOURCE)).isNotEqualTo(DIFFERENT, JSONCompareMode.LENIENT);
+ }
+
+ @Test
+ void isNotEqualToWhenResourcePathIsMatchingAndLenientShouldFail() {
+ assertThatExceptionOfType(AssertionError.class).isThrownBy(
+ () -> assertThat(forJson(SOURCE)).isNotEqualTo("lenient-same.json", JSONCompareMode.LENIENT));
+ }
+
+ @Test
+ void isNotEqualToWhenResourcePathIsNotMatchingAndLenientShouldPass() {
+ assertThat(forJson(SOURCE)).isNotEqualTo("different.json", JSONCompareMode.LENIENT);
+ }
+
+ @ParameterizedTest
+ @MethodSource("lenientSame")
+ void isNotEqualToWhenResourceIsMatchingAndLenientShouldFail(Resource expected) {
+ assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertThat(forJson(SOURCE))
+ .isNotEqualTo(expected, JSONCompareMode.LENIENT));
+ }
+
+ @ParameterizedTest
+ @MethodSource("different")
+ void isNotEqualToWhenResourceIsNotMatchingAndLenientShouldPass(Resource expected) {
+ assertThat(forJson(SOURCE)).isNotEqualTo(expected, JSONCompareMode.LENIENT);
+ }
+
+ @Test
+ void isNotEqualToWhenStringIsMatchingAndComparatorShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotEqualTo(LENIENT_SAME, comparator));
+ }
+
+ @Test
+ void isNotEqualToWhenStringIsNotMatchingAndComparatorShouldPass() {
+ assertThat(forJson(SOURCE)).isNotEqualTo(DIFFERENT, comparator);
+ }
+
+ @Test
+ void isNotEqualToWhenResourcePathIsMatchingAndComparatorShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotEqualTo("lenient-same.json", comparator));
+ }
+
+ @Test
+ void isNotEqualToWhenResourcePathIsNotMatchingAndComparatorShouldPass() {
+ assertThat(forJson(SOURCE)).isNotEqualTo("different.json", comparator);
+ }
+
+ @ParameterizedTest
+ @MethodSource("lenientSame")
+ void isNotEqualToWhenResourceIsMatchingAndComparatorShouldFail(Resource expected) {
+ assertThatExceptionOfType(AssertionError.class).isThrownBy(
+ () -> assertThat(forJson(SOURCE)).isNotEqualTo(expected, comparator));
+ }
+
+ @ParameterizedTest
+ @MethodSource("different")
+ void isNotEqualToWhenResourceIsNotMatchingAndComparatorShouldPass(Resource expected) {
+ assertThat(forJson(SOURCE)).isNotEqualTo(expected, comparator);
+ }
+
+ @Test
+ void isNotEqualToWhenResourceIsMatchingAndComparatorShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotEqualTo(createResource(LENIENT_SAME), comparator));
+ }
+
+ @Test
+ void isNotEqualToWhenResourceIsNotMatchingAndComparatorShouldPass() {
+ assertThat(forJson(SOURCE)).isNotEqualTo(createResource(DIFFERENT), comparator);
+ }
+
+ @Test
+ void isNotLenientlyEqualToWhenNullActualShouldPass() {
+ assertThat(forJson(null)).isNotLenientlyEqualTo(SOURCE);
+ }
+
+ @Test
+ void isNotLenientlyEqualToWhenStringIsNotMatchingShouldPass() {
+ assertThat(forJson(SOURCE)).isNotLenientlyEqualTo(DIFFERENT);
+ }
+
+ @Test
+ void isNotLenientlyEqualToWhenResourcePathIsMatchingShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotLenientlyEqualTo("lenient-same.json"));
+ }
+
+ @Test
+ void isNotLenientlyEqualToWhenResourcePathIsNotMatchingShouldPass() {
+ assertThat(forJson(SOURCE)).isNotLenientlyEqualTo("different.json");
+ }
+
+ @ParameterizedTest
+ @MethodSource("lenientSame")
+ void isNotLenientlyEqualToWhenResourceIsMatchingShouldFail(Resource expected) {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotLenientlyEqualTo(expected));
+ }
+
+ @ParameterizedTest
+ @MethodSource("different")
+ void isNotLenientlyEqualToWhenResourceIsNotMatchingShouldPass(Resource expected) {
+ assertThat(forJson(SOURCE)).isNotLenientlyEqualTo(expected);
+ }
+
+ @Test
+ void isNotStrictlyEqualToWhenStringIsMatchingShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotStrictlyEqualTo(SOURCE));
+ }
+
+ @Test
+ void isNotStrictlyEqualToWhenStringIsNotMatchingShouldPass() {
+ assertThat(forJson(SOURCE)).isNotStrictlyEqualTo(LENIENT_SAME);
+ }
+
+ @Test
+ void isNotStrictlyEqualToWhenResourcePathIsMatchingShouldFail() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotStrictlyEqualTo("source.json"));
+ }
+
+ @Test
+ void isNotStrictlyEqualToWhenResourcePathIsNotMatchingShouldPass() {
+ assertThat(forJson(SOURCE)).isNotStrictlyEqualTo("lenient-same.json");
+ }
+
+ @ParameterizedTest
+ @MethodSource("source")
+ void isNotStrictlyEqualToWhenResourceIsMatchingShouldFail(Resource expected) {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotStrictlyEqualTo(expected));
+ }
+
+ @ParameterizedTest
+ @MethodSource("lenientSame")
+ void isNotStrictlyEqualToWhenResourceIsNotMatchingShouldPass(Resource expected) {
+ assertThat(forJson(SOURCE)).isNotStrictlyEqualTo(expected);
+ }
+
+ @Test
+ void withResourceLoadClassShouldAllowToLoadRelativeContent() {
+ AbstractJsonContentAssert> jsonAssert = assertThat(forJson(NULLS)).withResourceLoadClass(String.class);
+ assertThatIllegalStateException()
+ .isThrownBy(() -> jsonAssert.isLenientlyEqualTo("nulls.json"))
+ .withMessage("Unable to load JSON from class path resource [java/lang/nulls.json]");
+
+ assertThat(forJson(NULLS)).withResourceLoadClass(JsonContent.class).isLenientlyEqualTo("nulls.json");
+ }
+
+ private AssertProvider> forJson(@Nullable String json) {
+ return () -> new TestJsonContentAssert(json, null).withResourceLoadClass(getClass());
+ }
+ }
+
+
+ private Consumer hasFailedToMatchPath(String expression) {
+ return error -> assertThat(error.getMessage()).containsSubsequence("Expecting:",
+ "To match JSON path:", "\"" + expression + "\"");
+ }
+
+ private Consumer hasFailedToNotMatchPath(String expression) {
+ return error -> assertThat(error.getMessage()).containsSubsequence("Expecting:",
+ "Not to match JSON path:", "\"" + expression + "\"");
+ }
+
+ private Path createFile(String content) {
+ try {
+ Path temp = Files.createTempFile("file", ".json");
+ Files.writeString(temp, content);
+ return temp;
+ }
+ catch (IOException ex) {
+ throw new IllegalStateException(ex);
+ }
+ }
+
+ private InputStream createInputStream(String content) {
+ return new ByteArrayInputStream(content.getBytes());
+ }
+
+ private Resource createResource(String content) {
+ return new ByteArrayResource(content.getBytes());
+ }
+
+ private static String loadJson(String path) {
+ try {
+ ClassPathResource resource = new ClassPathResource(path, AbstractJsonContentAssertTests.class);
+ return new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
+ }
+ catch (Exception ex) {
+ throw new IllegalStateException(ex);
+ }
+
+ }
+
+ private AssertProvider> forJson(@Nullable String json) {
+ return () -> new TestJsonContentAssert(json, null);
+ }
+
+ private static class TestJsonContentAssert extends AbstractJsonContentAssert {
+
+ public TestJsonContentAssert(@Nullable String json, @Nullable GenericHttpMessageConverter jsonMessageConverter) {
+ super(json, jsonMessageConverter, TestJsonContentAssert.class);
+ }
+ }
+
+}
diff --git a/spring-test/src/test/java/org/springframework/test/json/JsonContentAssertTests.java b/spring-test/src/test/java/org/springframework/test/json/JsonContentAssertTests.java
deleted file mode 100644
index 02c839bd8e..0000000000
--- a/spring-test/src/test/java/org/springframework/test/json/JsonContentAssertTests.java
+++ /dev/null
@@ -1,479 +0,0 @@
-/*
- * Copyright 2002-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.test.json;
-
-import java.io.ByteArrayInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.nio.file.Files;
-import java.nio.file.Path;
-import java.util.stream.Stream;
-
-import org.assertj.core.api.AssertProvider;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.api.TestInstance;
-import org.junit.jupiter.api.TestInstance.Lifecycle;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-import org.skyscreamer.jsonassert.JSONCompareMode;
-import org.skyscreamer.jsonassert.comparator.DefaultComparator;
-import org.skyscreamer.jsonassert.comparator.JSONComparator;
-
-import org.springframework.core.io.ByteArrayResource;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.core.io.FileSystemResource;
-import org.springframework.core.io.InputStreamResource;
-import org.springframework.core.io.Resource;
-import org.springframework.lang.Nullable;
-import org.springframework.util.FileCopyUtils;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
-
-/**
- * Tests for {@link JsonContentAssert}.
- *
- * @author Stephane Nicoll
- * @author Phillip Webb
- */
-@TestInstance(Lifecycle.PER_CLASS)
-class JsonContentAssertTests {
-
- private static final String SOURCE = loadJson("source.json");
-
- private static final String LENIENT_SAME = loadJson("lenient-same.json");
-
- private static final String DIFFERENT = loadJson("different.json");
-
- private static final JSONComparator COMPARATOR = new DefaultComparator(JSONCompareMode.LENIENT);
-
- @Test
- void isEqualToWhenStringIsMatchingShouldPass() {
- assertThat(forJson(SOURCE)).isEqualTo(SOURCE);
- }
-
- @Test
- void isEqualToWhenNullActualShouldFail() {
- assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
- assertThat(forJson(null)).isEqualTo(SOURCE));
- }
-
- @Test
- void isEqualToWhenExpectedIsNotAStringShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo(SOURCE.getBytes()));
- }
-
- @Test
- void isEqualToWhenExpectedIsNullShouldFail() {
- CharSequence actual = null;
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo(actual, JSONCompareMode.LENIENT));
- }
-
- @Test
- void isEqualToWhenStringIsMatchingAndLenientShouldPass() {
- assertThat(forJson(SOURCE)).isEqualTo(LENIENT_SAME, JSONCompareMode.LENIENT);
- }
-
- @Test
- void isEqualToWhenStringIsNotMatchingAndLenientShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo(DIFFERENT, JSONCompareMode.LENIENT));
- }
-
- @Test
- void isEqualToWhenResourcePathIsMatchingAndLenientShouldPass() {
- assertThat(forJson(SOURCE)).isEqualTo("lenient-same.json", JSONCompareMode.LENIENT);
- }
-
- @Test
- void isEqualToWhenResourcePathIsNotMatchingAndLenientShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo("different.json", JSONCompareMode.LENIENT));
- }
-
- Stream source() {
- return Stream.of(
- Arguments.of(new ClassPathResource("source.json", JsonContentAssertTests.class)),
- Arguments.of(new ByteArrayResource(SOURCE.getBytes())),
- Arguments.of(new FileSystemResource(createFile(SOURCE))),
- Arguments.of(new InputStreamResource(createInputStream(SOURCE))));
- }
-
- Stream lenientSame() {
- return Stream.of(
- Arguments.of(new ClassPathResource("lenient-same.json", JsonContentAssertTests.class)),
- Arguments.of(new ByteArrayResource(LENIENT_SAME.getBytes())),
- Arguments.of(new FileSystemResource(createFile(LENIENT_SAME))),
- Arguments.of(new InputStreamResource(createInputStream(LENIENT_SAME))));
- }
-
- Stream different() {
- return Stream.of(
- Arguments.of(new ClassPathResource("different.json", JsonContentAssertTests.class)),
- Arguments.of(new ByteArrayResource(DIFFERENT.getBytes())),
- Arguments.of(new FileSystemResource(createFile(DIFFERENT))),
- Arguments.of(new InputStreamResource(createInputStream(DIFFERENT))));
- }
-
- @ParameterizedTest
- @MethodSource("lenientSame")
- void isEqualToWhenResourceIsMatchingAndLenientSameShouldPass(Resource expected) {
- assertThat(forJson(SOURCE)).isEqualTo(expected, JSONCompareMode.LENIENT);
- }
-
- @ParameterizedTest
- @MethodSource("different")
- void isEqualToWhenResourceIsNotMatchingAndLenientShouldFail(Resource expected) {
- assertThatExceptionOfType(AssertionError.class).isThrownBy(
- () -> assertThat(forJson(SOURCE)).isEqualTo(expected, JSONCompareMode.LENIENT));
- }
-
-
- @Test
- void isEqualToWhenStringIsMatchingAndComparatorShouldPass() {
- assertThat(forJson(SOURCE)).isEqualTo(LENIENT_SAME, COMPARATOR);
- }
-
- @Test
- void isEqualToWhenStringIsNotMatchingAndComparatorShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo(DIFFERENT, COMPARATOR));
- }
-
- @Test
- void isEqualToWhenResourcePathIsMatchingAndComparatorShouldPass() {
- assertThat(forJson(SOURCE)).isEqualTo("lenient-same.json", COMPARATOR);
- }
-
- @Test
- void isEqualToWhenResourcePathIsNotMatchingAndComparatorShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo("different.json", COMPARATOR));
- }
-
- @ParameterizedTest
- @MethodSource("lenientSame")
- void isEqualToWhenResourceIsMatchingAndComparatorShouldPass(Resource expected) {
- assertThat(forJson(SOURCE)).isEqualTo(expected, COMPARATOR);
- }
-
- @ParameterizedTest
- @MethodSource("different")
- void isEqualToWhenResourceIsNotMatchingAndComparatorShouldFail(Resource expected) {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isEqualTo(expected, COMPARATOR));
- }
-
- @Test
- void isLenientlyEqualToWhenStringIsMatchingShouldPass() {
- assertThat(forJson(SOURCE)).isLenientlyEqualTo(LENIENT_SAME);
- }
-
- @Test
- void isLenientlyEqualToWhenNullActualShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(null)).isLenientlyEqualTo(SOURCE));
- }
-
- @Test
- void isLenientlyEqualToWhenStringIsNotMatchingShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isLenientlyEqualTo(DIFFERENT));
- }
-
- @Test
- void isLenientlyEqualToWhenExpectedDoesNotExistShouldFail() {
- assertThatIllegalStateException()
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isLenientlyEqualTo("does-not-exist.json"))
- .withMessage("Unable to load JSON from class path resource [org/springframework/test/json/does-not-exist.json]");
- }
-
- @Test
- void isLenientlyEqualToWhenResourcePathIsMatchingShouldPass() {
- assertThat(forJson(SOURCE)).isLenientlyEqualTo("lenient-same.json");
- }
-
- @Test
- void isLenientlyEqualToWhenResourcePathIsNotMatchingShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isLenientlyEqualTo("different.json"));
- }
-
- @ParameterizedTest
- @MethodSource("lenientSame")
- void isLenientlyEqualToWhenResourceIsMatchingShouldPass(Resource expected) {
- assertThat(forJson(SOURCE)).isLenientlyEqualTo(expected);
- }
-
- @ParameterizedTest
- @MethodSource("different")
- void isLenientlyEqualToWhenResourceIsNotMatchingShouldFail(Resource expected) {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isLenientlyEqualTo(expected));
- }
-
- @Test
- void isStrictlyEqualToWhenStringIsMatchingShouldPass() {
- assertThat(forJson(SOURCE)).isStrictlyEqualTo(SOURCE);
- }
-
- @Test
- void isStrictlyEqualToWhenStringIsNotMatchingShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isStrictlyEqualTo(LENIENT_SAME));
- }
-
- @Test
- void isStrictlyEqualToWhenResourcePathIsMatchingShouldPass() {
- assertThat(forJson(SOURCE)).isStrictlyEqualTo("source.json");
- }
-
- @Test
- void isStrictlyEqualToWhenResourcePathIsNotMatchingShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isStrictlyEqualTo("lenient-same.json"));
- }
-
- @ParameterizedTest
- @MethodSource("source")
- void isStrictlyEqualToWhenResourceIsMatchingShouldPass(Resource expected) {
- assertThat(forJson(SOURCE)).isStrictlyEqualTo(expected);
- }
-
- @ParameterizedTest
- @MethodSource("lenientSame")
- void isStrictlyEqualToWhenResourceIsNotMatchingShouldFail(Resource expected) {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isStrictlyEqualTo(expected));
- }
-
-
- @Test
- void isNotEqualToWhenStringIsMatchingShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotEqualTo(SOURCE));
- }
-
- @Test
- void isNotEqualToWhenNullActualShouldPass() {
- assertThat(forJson(null)).isNotEqualTo(SOURCE);
- }
-
- @Test
- void isNotEqualToWhenStringIsNotMatchingShouldPass() {
- assertThat(forJson(SOURCE)).isNotEqualTo(DIFFERENT);
- }
-
- @Test
- void isNotEqualToAsObjectWhenExpectedIsNotAStringShouldNotFail() {
- assertThat(forJson(SOURCE)).isNotEqualTo(SOURCE.getBytes());
- }
-
- @Test
- void isNotEqualToWhenStringIsMatchingAndLenientShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotEqualTo(LENIENT_SAME, JSONCompareMode.LENIENT));
- }
-
- @Test
- void isNotEqualToWhenStringIsNotMatchingAndLenientShouldPass() {
- assertThat(forJson(SOURCE)).isNotEqualTo(DIFFERENT, JSONCompareMode.LENIENT);
- }
-
- @Test
- void isNotEqualToWhenResourcePathIsMatchingAndLenientShouldFail() {
- assertThatExceptionOfType(AssertionError.class).isThrownBy(
- () -> assertThat(forJson(SOURCE)).isNotEqualTo("lenient-same.json", JSONCompareMode.LENIENT));
- }
-
- @Test
- void isNotEqualToWhenResourcePathIsNotMatchingAndLenientShouldPass() {
- assertThat(forJson(SOURCE)).isNotEqualTo("different.json", JSONCompareMode.LENIENT);
- }
-
- @ParameterizedTest
- @MethodSource("lenientSame")
- void isNotEqualToWhenResourceIsMatchingAndLenientShouldFail(Resource expected) {
- assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertThat(forJson(SOURCE))
- .isNotEqualTo(expected, JSONCompareMode.LENIENT));
- }
-
- @ParameterizedTest
- @MethodSource("different")
- void isNotEqualToWhenResourceIsNotMatchingAndLenientShouldPass(Resource expected) {
- assertThat(forJson(SOURCE)).isNotEqualTo(expected, JSONCompareMode.LENIENT);
- }
-
- @Test
- void isNotEqualToWhenStringIsMatchingAndComparatorShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotEqualTo(LENIENT_SAME, COMPARATOR));
- }
-
- @Test
- void isNotEqualToWhenStringIsNotMatchingAndComparatorShouldPass() {
- assertThat(forJson(SOURCE)).isNotEqualTo(DIFFERENT, COMPARATOR);
- }
-
- @Test
- void isNotEqualToWhenResourcePathIsMatchingAndComparatorShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotEqualTo("lenient-same.json", COMPARATOR));
- }
-
- @Test
- void isNotEqualToWhenResourcePathIsNotMatchingAndComparatorShouldPass() {
- assertThat(forJson(SOURCE)).isNotEqualTo("different.json", COMPARATOR);
- }
-
- @ParameterizedTest
- @MethodSource("lenientSame")
- void isNotEqualToWhenResourceIsMatchingAndComparatorShouldFail(Resource expected) {
- assertThatExceptionOfType(AssertionError.class).isThrownBy(
- () -> assertThat(forJson(SOURCE)).isNotEqualTo(expected, COMPARATOR));
- }
-
- @ParameterizedTest
- @MethodSource("different")
- void isNotEqualToWhenResourceIsNotMatchingAndComparatorShouldPass(Resource expected) {
- assertThat(forJson(SOURCE)).isNotEqualTo(expected, COMPARATOR);
- }
-
- @Test
- void isNotEqualToWhenResourceIsMatchingAndComparatorShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotEqualTo(createResource(LENIENT_SAME), COMPARATOR));
- }
-
- @Test
- void isNotEqualToWhenResourceIsNotMatchingAndComparatorShouldPass() {
- assertThat(forJson(SOURCE)).isNotEqualTo(createResource(DIFFERENT), COMPARATOR);
- }
-
- @Test
- void isNotLenientlyEqualToWhenNullActualShouldPass() {
- assertThat(forJson(null)).isNotLenientlyEqualTo(SOURCE);
- }
-
- @Test
- void isNotLenientlyEqualToWhenStringIsNotMatchingShouldPass() {
- assertThat(forJson(SOURCE)).isNotLenientlyEqualTo(DIFFERENT);
- }
-
- @Test
- void isNotLenientlyEqualToWhenResourcePathIsMatchingShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotLenientlyEqualTo("lenient-same.json"));
- }
-
- @Test
- void isNotLenientlyEqualToWhenResourcePathIsNotMatchingShouldPass() {
- assertThat(forJson(SOURCE)).isNotLenientlyEqualTo("different.json");
- }
-
- @ParameterizedTest
- @MethodSource("lenientSame")
- void isNotLenientlyEqualToWhenResourceIsMatchingShouldFail(Resource expected) {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotLenientlyEqualTo(expected));
- }
-
- @ParameterizedTest
- @MethodSource("different")
- void isNotLenientlyEqualToWhenResourceIsNotMatchingShouldPass(Resource expected) {
- assertThat(forJson(SOURCE)).isNotLenientlyEqualTo(expected);
- }
-
- @Test
- void isNotStrictlyEqualToWhenStringIsMatchingShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotStrictlyEqualTo(SOURCE));
- }
-
- @Test
- void isNotStrictlyEqualToWhenStringIsNotMatchingShouldPass() {
- assertThat(forJson(SOURCE)).isNotStrictlyEqualTo(LENIENT_SAME);
- }
-
- @Test
- void isNotStrictlyEqualToWhenResourcePathIsMatchingShouldFail() {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotStrictlyEqualTo("source.json"));
- }
-
- @Test
- void isNotStrictlyEqualToWhenResourcePathIsNotMatchingShouldPass() {
- assertThat(forJson(SOURCE)).isNotStrictlyEqualTo("lenient-same.json");
- }
-
- @ParameterizedTest
- @MethodSource("source")
- void isNotStrictlyEqualToWhenResourceIsMatchingShouldFail(Resource expected) {
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SOURCE)).isNotStrictlyEqualTo(expected));
- }
-
- @ParameterizedTest
- @MethodSource("lenientSame")
- void isNotStrictlyEqualToWhenResourceIsNotMatchingShouldPass(Resource expected) {
- assertThat(forJson(SOURCE)).isNotStrictlyEqualTo(expected);
- }
-
- @Test
- void isNullWhenActualIsNullShouldPass() {
- assertThat(forJson(null)).isNull();
- }
-
- private Path createFile(String content) {
- try {
- Path temp = Files.createTempFile("file", ".json");
- Files.writeString(temp, content);
- return temp;
- }
- catch (IOException ex) {
- throw new IllegalStateException(ex);
- }
- }
-
- private InputStream createInputStream(String content) {
- return new ByteArrayInputStream(content.getBytes());
- }
-
- private Resource createResource(String content) {
- return new ByteArrayResource(content.getBytes());
- }
-
- private static String loadJson(String path) {
- try {
- ClassPathResource resource = new ClassPathResource(path, JsonContentAssertTests.class);
- return new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
- }
- catch (Exception ex) {
- throw new IllegalStateException(ex);
- }
-
- }
-
- private AssertProvider forJson(@Nullable String json) {
- return () -> new JsonContentAssert(json, JsonContentAssertTests.class);
- }
-
-}
diff --git a/spring-test/src/test/java/org/springframework/test/json/JsonPathAssertTests.java b/spring-test/src/test/java/org/springframework/test/json/JsonPathAssertTests.java
deleted file mode 100644
index d1a8968628..0000000000
--- a/spring-test/src/test/java/org/springframework/test/json/JsonPathAssertTests.java
+++ /dev/null
@@ -1,323 +0,0 @@
-/*
- * Copyright 2002-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.test.json;
-
-import java.util.List;
-import java.util.Map;
-import java.util.function.Consumer;
-import java.util.stream.Stream;
-
-import com.fasterxml.jackson.databind.ObjectMapper;
-import org.assertj.core.api.AssertProvider;
-import org.junit.jupiter.api.Nested;
-import org.junit.jupiter.api.Test;
-import org.junit.jupiter.params.ParameterizedTest;
-import org.junit.jupiter.params.provider.Arguments;
-import org.junit.jupiter.params.provider.MethodSource;
-import org.junit.jupiter.params.provider.ValueSource;
-
-import org.springframework.core.ParameterizedTypeReference;
-import org.springframework.core.io.ClassPathResource;
-import org.springframework.http.converter.GenericHttpMessageConverter;
-import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
-import org.springframework.lang.Nullable;
-import org.springframework.util.FileCopyUtils;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import static org.assertj.core.api.Assertions.assertThatIllegalStateException;
-import static org.assertj.core.api.Assertions.entry;
-
-/**
- * Tests for {@link JsonPathAssert}.
- *
- * @author Phillip Webb
- * @author Stephane Nicoll
- */
-class JsonPathAssertTests {
-
- private static final String TYPES = loadJson("types.json");
-
- private static final String SIMPSONS = loadJson("simpsons.json");
-
- private static final String NULLS = loadJson("nulls.json");
-
- private static final MappingJackson2HttpMessageConverter jsonHttpMessageConverter =
- new MappingJackson2HttpMessageConverter(new ObjectMapper());
-
-
- @Nested
- class HasPathTests {
-
- @Test
- void hasPathForPresentAndNotNull() {
- assertThat(forJson(NULLS)).hasPath("$.valuename");
- }
-
- @Test
- void hasPathForPresentAndNull() {
- assertThat(forJson(NULLS)).hasPath("$.nullname");
- }
-
- @Test
- void hasPathForOperatorMatching() {
- assertThat(forJson(SIMPSONS)).
- hasPath("$.familyMembers[?(@.name == 'Homer')]");
- }
-
- @Test
- void hasPathForOperatorNotMatching() {
- assertThat(forJson(SIMPSONS)).
- hasPath("$.familyMembers[?(@.name == 'Dilbert')]");
- }
-
- @Test
- void hasPathForNotPresent() {
- String expression = "$.missing";
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(NULLS)).hasPath(expression))
- .satisfies(hasFailedToMatchPath("$.missing"));
- }
-
- @Test
- void hasPathSatisfying() {
- assertThat(forJson(TYPES)).hasPathSatisfying("$.str", value -> assertThat(value).isEqualTo("foo"))
- .hasPathSatisfying("$.num", value -> assertThat(value).isEqualTo(5));
- }
-
- @Test
- void hasPathSatisfyingForPathNotPresent() {
- String expression = "missing";
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(NULLS)).hasPathSatisfying(expression, value -> {}))
- .satisfies(hasFailedToMatchPath(expression));
- }
-
- @Test
- void doesNotHavePathForMissing() {
- assertThat(forJson(NULLS)).doesNotHavePath("$.missing");
- }
-
-
- @Test
- void doesNotHavePathForPresent() {
- String expression = "$.valuename";
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(NULLS)).doesNotHavePath(expression))
- .satisfies(hasFailedToNotMatchPath(expression));
- }
- }
-
-
- @Nested
- class ExtractingPathTests {
-
- @Test
- void isNullWithNullPathValue() {
- assertThat(forJson(NULLS)).extractingPath("$.nullname").isNull();
- }
-
- @ParameterizedTest
- @ValueSource(strings = { "$.str", "$.emptyString", "$.num", "$.bool", "$.arr",
- "$.emptyArray", "$.colorMap", "$.emptyMap" })
- void isNotNullWithValue(String path) {
- assertThat(forJson(TYPES)).extractingPath(path).isNotNull();
- }
-
- @ParameterizedTest
- @MethodSource
- void isEqualToOnRawValue(String path, Object expected) {
- assertThat(forJson(TYPES)).extractingPath(path).isEqualTo(expected);
- }
-
- static Stream isEqualToOnRawValue() {
- return Stream.of(
- Arguments.of("$.str", "foo"),
- Arguments.of("$.num", 5),
- Arguments.of("$.bool", true),
- Arguments.of("$.arr", List.of(42)),
- Arguments.of("$.colorMap", Map.of("red", "rojo")));
- }
-
- @Test
- void asStringWithActualValue() {
- assertThat(forJson(TYPES)).extractingPath("@.str").asString().startsWith("f").endsWith("o");
- }
-
- @Test
- void asStringIsEmpty() {
- assertThat(forJson(TYPES)).extractingPath("@.emptyString").asString().isEmpty();
- }
-
- @Test
- void asNumberWithActualValue() {
- assertThat(forJson(TYPES)).extractingPath("@.num").asNumber().isEqualTo(5);
- }
-
- @Test
- void asBooleanWithActualValue() {
- assertThat(forJson(TYPES)).extractingPath("@.bool").asBoolean().isTrue();
- }
-
- @Test
- void asArrayWithActualValue() {
- assertThat(forJson(TYPES)).extractingPath("@.arr").asArray().containsOnly(42);
- }
-
- @Test
- void asArrayIsEmpty() {
- assertThat(forJson(TYPES)).extractingPath("@.emptyArray").asArray().isEmpty();
- }
-
- @Test
- void asArrayWithFilterPredicatesMatching() {
- assertThat(forJson(SIMPSONS))
- .extractingPath("$.familyMembers[?(@.name == 'Bart')]").asArray().hasSize(1);
- }
-
- @Test
- void asArrayWithFilterPredicatesNotMatching() {
- assertThat(forJson(SIMPSONS)).
- extractingPath("$.familyMembers[?(@.name == 'Dilbert')]").asArray().isEmpty();
- }
-
- @Test
- void asMapWithActualValue() {
- assertThat(forJson(TYPES)).extractingPath("@.colorMap").asMap().containsOnly(entry("red", "rojo"));
- }
-
- @Test
- void asMapIsEmpty() {
- assertThat(forJson(TYPES)).extractingPath("@.emptyMap").asMap().isEmpty();
- }
-
- @Test
- void convertToWithoutHttpMessageConverterShouldFail() {
- JsonPathValueAssert path = assertThat(forJson(SIMPSONS)).extractingPath("$.familyMembers[0]");
- assertThatIllegalStateException()
- .isThrownBy(() -> path.convertTo(Member.class))
- .withMessage("No JSON message converter available to convert {name=Homer}");
- }
-
- @Test
- void convertToTargetType() {
- assertThat(forJson(SIMPSONS, jsonHttpMessageConverter))
- .extractingPath("$.familyMembers[0]").convertTo(Member.class)
- .satisfies(member -> assertThat(member.name).isEqualTo("Homer"));
- }
-
- @Test
- void convertToIncompatibleTargetTypeShouldFail() {
- JsonPathValueAssert path = assertThat(forJson(SIMPSONS, jsonHttpMessageConverter))
- .extractingPath("$.familyMembers[0]");
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> path.convertTo(Customer.class))
- .withMessageContainingAll("Expected value at JSON path \"$.familyMembers[0]\":",
- Customer.class.getName(), "name");
- }
-
- @Test
- void convertArrayToParameterizedType() {
- assertThat(forJson(SIMPSONS, jsonHttpMessageConverter))
- .extractingPath("$.familyMembers")
- .convertTo(new ParameterizedTypeReference>() {})
- .satisfies(family -> assertThat(family).hasSize(5).element(0).isEqualTo(new Member("Homer")));
- }
-
- @Test
- void isEmptyWithPathHavingNullValue() {
- assertThat(forJson(NULLS)).extractingPath("nullname").isEmpty();
- }
-
- @ParameterizedTest
- @ValueSource(strings = { "$.emptyString", "$.emptyArray", "$.emptyMap" })
- void isEmptyWithEmptyValue(String path) {
- assertThat(forJson(TYPES)).extractingPath(path).isEmpty();
- }
-
- @Test
- void isEmptyForPathWithFilterMatching() {
- String expression = "$.familyMembers[?(@.name == 'Bart')]";
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SIMPSONS)).extractingPath(expression).isEmpty())
- .withMessageContainingAll("Expected value at JSON path \"" + expression + "\"",
- "[{\"name\":\"Bart\"}]", "To be empty");
- }
-
- @Test
- void isEmptyForPathWithFilterNotMatching() {
- assertThat(forJson(SIMPSONS)).extractingPath("$.familyMembers[?(@.name == 'Dilbert')]").isEmpty();
- }
-
- @ParameterizedTest
- @ValueSource(strings = { "$.str", "$.num", "$.bool", "$.arr", "$.colorMap" })
- void isNotEmptyWithNonNullValue(String path) {
- assertThat(forJson(TYPES)).extractingPath(path).isNotEmpty();
- }
-
- @Test
- void isNotEmptyForPathWithFilterMatching() {
- assertThat(forJson(SIMPSONS)).extractingPath("$.familyMembers[?(@.name == 'Bart')]").isNotEmpty();
- }
-
- @Test
- void isNotEmptyForPathWithFilterNotMatching() {
- String expression = "$.familyMembers[?(@.name == 'Dilbert')]";
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(forJson(SIMPSONS)).extractingPath(expression).isNotEmpty())
- .withMessageContainingAll("Expected value at JSON path \"" + expression + "\"",
- "To not be empty");
- }
-
-
- private record Member(String name) {}
-
- private record Customer(long id, String username) {}
-
- }
-
- private Consumer hasFailedToMatchPath(String expression) {
- return error -> assertThat(error.getMessage()).containsSubsequence("Expecting:",
- "To match JSON path:", "\"" + expression + "\"");
- }
-
- private Consumer hasFailedToNotMatchPath(String expression) {
- return error -> assertThat(error.getMessage()).containsSubsequence("Expecting:",
- "Not to match JSON path:", "\"" + expression + "\"");
- }
-
-
- private static String loadJson(String path) {
- try {
- ClassPathResource resource = new ClassPathResource(path, JsonPathAssertTests.class);
- return new String(FileCopyUtils.copyToByteArray(resource.getInputStream()));
- }
- catch (Exception ex) {
- throw new IllegalStateException(ex);
- }
- }
-
- private AssertProvider forJson(String json) {
- return forJson(json, null);
- }
-
- private AssertProvider forJson(String json,
- @Nullable GenericHttpMessageConverter jsonHttpMessageConverter) {
- return () -> new JsonPathAssert(json, jsonHttpMessageConverter);
- }
-
-}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssertTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssertTests.java
index 0d28a4ea93..1344ada365 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssertTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AbstractHttpServletResponseAssertTests.java
@@ -23,6 +23,7 @@ import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpStatus;
+import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletResponse;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -37,13 +38,30 @@ class AbstractHttpServletResponseAssertTests {
@Nested
class HeadersTests {
+ @Test
+ void containsHeader() {
+ MockHttpServletResponse response = createResponse(Map.of("n1", "v1", "n2", "v2", "n3", "v3"));
+ assertThat(response).containsHeader("n1");
+ }
+
+ @Test
+ void doesNotContainHeader() {
+ MockHttpServletResponse response = createResponse(Map.of("n1", "v1", "n2", "v2", "n3", "v3"));
+ assertThat(response).doesNotContainHeader("n4");
+ }
+
+ @Test
+ void hasHeader() {
+ MockHttpServletResponse response = createResponse(Map.of("n1", "v1", "n2", "v2", "n3", "v3"));
+ assertThat(response).hasHeader("n1", "v1");
+ }
+
@Test
void headersAreMatching() {
MockHttpServletResponse response = createResponse(Map.of("n1", "v1", "n2", "v2", "n3", "v3"));
assertThat(response).headers().containsHeaders("n1", "n2", "n3");
}
-
private MockHttpServletResponse createResponse(Map headers) {
MockHttpServletResponse response = new MockHttpServletResponse();
headers.forEach(response::addHeader);
@@ -51,6 +69,45 @@ class AbstractHttpServletResponseAssertTests {
}
}
+ @Nested
+ class ContentTypeTests {
+
+ @Test
+ void contentType() {
+ MockHttpServletResponse response = createResponse("text/plain");
+ assertThat(response).hasContentType(MediaType.TEXT_PLAIN);
+ }
+
+ @Test
+ void contentTypeAndRepresentation() {
+ MockHttpServletResponse response = createResponse("text/plain");
+ assertThat(response).hasContentType("text/plain");
+ }
+
+ @Test
+ void contentTypeCompatibleWith() {
+ MockHttpServletResponse response = createResponse("application/json;charset=UTF-8");
+ assertThat(response).hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON);
+ }
+
+ @Test
+ void contentTypeCompatibleWithAndStringRepresentation() {
+ MockHttpServletResponse response = createResponse("text/plain");
+ assertThat(response).hasContentTypeCompatibleWith("text/*");
+ }
+
+ @Test
+ void contentTypeCanBeAsserted() {
+ MockHttpServletResponse response = createResponse("text/plain");
+ assertThat(response).contentType().isInstanceOf(MediaType.class).isCompatibleWith("text/*").isNotNull();
+ }
+
+ private MockHttpServletResponse createResponse(String contentType) {
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ response.setContentType(contentType);
+ return response;
+ }
+ }
@Nested
class StatusTests {
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AbstractMockHttpServletResponseAssertTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AbstractMockHttpServletResponseAssertTests.java
index 6c0de703ac..18f8b71103 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AbstractMockHttpServletResponseAssertTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AbstractMockHttpServletResponseAssertTests.java
@@ -16,15 +16,17 @@
package org.springframework.test.web.servlet.assertj;
-
import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets;
+import org.assertj.core.api.AssertProvider;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.mock.web.MockHttpServletResponse;
+import org.springframework.test.json.JsonContent;
+import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
/**
@@ -34,12 +36,50 @@ import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
*/
public class AbstractMockHttpServletResponseAssertTests {
+ @Test
+ void bodyText() {
+ MockHttpServletResponse response = createResponse("OK");
+ assertThat(fromResponse(response)).bodyText().isEqualTo("OK");
+ }
+
+ @Test
+ void bodyJsonWithJsonPath() {
+ MockHttpServletResponse response = createResponse("{\"albumById\": {\"name\": \"Greatest hits\"}}");
+ assertThat(fromResponse(response)).bodyJson()
+ .extractingPath("$.albumById.name").isEqualTo("Greatest hits");
+ }
+
+ @Test
+ void bodyJsonCanLoadResourceRelativeToClass() {
+ MockHttpServletResponse response = createResponse("{ \"name\" : \"Spring\", \"age\" : 123 }");
+ // See org/springframework/test/json/example.json
+ assertThat(fromResponse(response)).bodyJson().withResourceLoadClass(JsonContent.class)
+ .isLenientlyEqualTo("example.json");
+ }
+
+ @Test
+ void bodyWithByteArray() throws UnsupportedEncodingException {
+ byte[] bytes = "OK".getBytes(StandardCharsets.UTF_8);
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ response.getWriter().write("OK");
+ response.setContentType(StandardCharsets.UTF_8.name());
+ assertThat(fromResponse(response)).body().isEqualTo(bytes);
+ }
+
+ @Test
+ void hasBodyTextEqualTo() throws UnsupportedEncodingException {
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ response.getWriter().write("OK");
+ response.setContentType(StandardCharsets.UTF_8.name());
+ assertThat(fromResponse(response)).hasBodyTextEqualTo("OK");
+ }
+
@Test
void hasForwardedUrl() {
String forwardedUrl = "https://example.com/42";
MockHttpServletResponse response = new MockHttpServletResponse();
response.setForwardedUrl(forwardedUrl);
- assertThat(response).hasForwardedUrl(forwardedUrl);
+ assertThat(fromResponse(response)).hasForwardedUrl(forwardedUrl);
}
@Test
@@ -48,7 +88,7 @@ public class AbstractMockHttpServletResponseAssertTests {
MockHttpServletResponse response = new MockHttpServletResponse();
response.setForwardedUrl(forwardedUrl);
assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(response).hasForwardedUrl("another"))
+ .isThrownBy(() -> assertThat(fromResponse(response)).hasForwardedUrl("another"))
.withMessageContainingAll("Forwarded URL", forwardedUrl, "another");
}
@@ -57,7 +97,7 @@ public class AbstractMockHttpServletResponseAssertTests {
String redirectedUrl = "https://example.com/42";
MockHttpServletResponse response = new MockHttpServletResponse();
response.addHeader(HttpHeaders.LOCATION, redirectedUrl);
- assertThat(response).hasRedirectedUrl(redirectedUrl);
+ assertThat(fromResponse(response)).hasRedirectedUrl(redirectedUrl);
}
@Test
@@ -66,29 +106,25 @@ public class AbstractMockHttpServletResponseAssertTests {
MockHttpServletResponse response = new MockHttpServletResponse();
response.addHeader(HttpHeaders.LOCATION, redirectedUrl);
assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> assertThat(response).hasRedirectedUrl("another"))
+ .isThrownBy(() -> assertThat(fromResponse(response)).hasRedirectedUrl("another"))
.withMessageContainingAll("Redirected URL", redirectedUrl, "another");
}
- @Test
- void bodyHasContent() throws UnsupportedEncodingException {
- MockHttpServletResponse response = new MockHttpServletResponse();
- response.getWriter().write("OK");
- assertThat(response).body().asString().isEqualTo("OK");
+
+ private MockHttpServletResponse createResponse(String body) {
+ try {
+ MockHttpServletResponse response = new MockHttpServletResponse();
+ response.setContentType(StandardCharsets.UTF_8.name());
+ response.getWriter().write(body);
+ return response;
+ }
+ catch (UnsupportedEncodingException ex) {
+ throw new IllegalStateException(ex);
+ }
}
- @Test
- void bodyHasContentWithResponseCharacterEncoding() throws UnsupportedEncodingException {
- byte[] bytes = "OK".getBytes(StandardCharsets.UTF_8);
- MockHttpServletResponse response = new MockHttpServletResponse();
- response.getWriter().write("OK");
- response.setContentType(StandardCharsets.UTF_8.name());
- assertThat(response).body().isEqualTo(bytes);
- }
-
-
- private static ResponseAssert assertThat(MockHttpServletResponse response) {
- return new ResponseAssert(response);
+ private static AssertProvider fromResponse(MockHttpServletResponse response) {
+ return () -> new ResponseAssert(response);
}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/DefaultAssertableMvcResultTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/DefaultMvcTestResultTests.java
similarity index 57%
rename from spring-test/src/test/java/org/springframework/test/web/servlet/assertj/DefaultAssertableMvcResultTests.java
rename to spring-test/src/test/java/org/springframework/test/web/servlet/assertj/DefaultMvcTestResultTests.java
index f59a53521d..e1e1867c89 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/DefaultAssertableMvcResultTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/DefaultMvcTestResultTests.java
@@ -30,76 +30,47 @@ import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
/**
- * Tests for {@link DefaultAssertableMvcResult}.
+ * Tests for {@link DefaultMvcTestResult}.
*
* @author Stephane Nicoll
*/
-class DefaultAssertableMvcResultTests {
+class DefaultMvcTestResultTests {
@Test
void createWithMvcResultDelegatesToIt() {
MockHttpServletRequest request = new MockHttpServletRequest();
MvcResult mvcResult = mock(MvcResult.class);
given(mvcResult.getRequest()).willReturn(request);
- DefaultAssertableMvcResult result = new DefaultAssertableMvcResult(mvcResult, null, null);
+ DefaultMvcTestResult result = new DefaultMvcTestResult(mvcResult, null, null);
assertThat(result.getRequest()).isSameAs(request);
verify(mvcResult).getRequest();
}
@Test
void createWithExceptionDoesNotAllowAccessToRequest() {
- assertRequestHasFailed(DefaultAssertableMvcResult::getRequest);
+ assertRequestHasFailed(DefaultMvcTestResult::getRequest);
}
@Test
void createWithExceptionDoesNotAllowAccessToResponse() {
- assertRequestHasFailed(DefaultAssertableMvcResult::getResponse);
+ assertRequestHasFailed(DefaultMvcTestResult::getResponse);
}
- @Test
- void createWithExceptionDoesNotAllowAccessToHandler() {
- assertRequestHasFailed(DefaultAssertableMvcResult::getHandler);
- }
-
- @Test
- void createWithExceptionDoesNotAllowAccessToInterceptors() {
- assertRequestHasFailed(DefaultAssertableMvcResult::getInterceptors);
- }
-
- @Test
- void createWithExceptionDoesNotAllowAccessToModelAndView() {
- assertRequestHasFailed(DefaultAssertableMvcResult::getModelAndView);
- }
@Test
void createWithExceptionDoesNotAllowAccessToResolvedException() {
- assertRequestHasFailed(DefaultAssertableMvcResult::getResolvedException);
- }
-
- @Test
- void createWithExceptionDoesNotAllowAccessToFlashMap() {
- assertRequestHasFailed(DefaultAssertableMvcResult::getFlashMap);
- }
-
- @Test
- void createWithExceptionDoesNotAllowAccessToAsyncResult() {
- assertRequestHasFailed(DefaultAssertableMvcResult::getAsyncResult);
- }
-
- @Test
- void createWithExceptionDoesNotAllowAccessToAsyncResultWithTimeToWait() {
- assertRequestHasFailed(result -> result.getAsyncResult(1000));
+ assertRequestHasFailed(DefaultMvcTestResult::getResolvedException);
}
@Test
void createWithExceptionReturnsException() {
IllegalStateException exception = new IllegalStateException("Expected");
- DefaultAssertableMvcResult result = new DefaultAssertableMvcResult(null, exception, null);
+ DefaultMvcTestResult result = new DefaultMvcTestResult(null, exception, null);
assertThat(result.getUnresolvedException()).isSameAs(exception);
}
- private void assertRequestHasFailed(Consumer action) {
- DefaultAssertableMvcResult result = new DefaultAssertableMvcResult(null, new IllegalStateException("Expected"), null);
+ private void assertRequestHasFailed(Consumer action) {
+ DefaultMvcTestResult result = new DefaultMvcTestResult(null, new IllegalStateException("Expected"), null);
assertThatIllegalStateException().isThrownBy(() -> action.accept(result))
.withMessageContaining("Request has failed with unresolved exception");
}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AssertableMockMvcIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java
similarity index 91%
rename from spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AssertableMockMvcIntegrationTests.java
rename to spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java
index 8e6f3eb735..1a6f69daa3 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AssertableMockMvcIntegrationTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java
@@ -43,7 +43,6 @@ import org.springframework.stereotype.Controller;
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.Person;
-import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
@@ -69,19 +68,19 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
/**
- * Integration tests for {@link AssertableMockMvc}.
+ * Integration tests for {@link MockMvcTester}.
*
* @author Brian Clozel
* @author Stephane Nicoll
*/
@SpringJUnitConfig
@WebAppConfiguration
-public class AssertableMockMvcIntegrationTests {
+public class MockMvcTesterIntegrationTests {
- private final AssertableMockMvc mockMvc;
+ private final MockMvcTester mockMvc;
- AssertableMockMvcIntegrationTests(WebApplicationContext wac) {
- this.mockMvc = AssertableMockMvc.from(wac);
+ MockMvcTesterIntegrationTests(WebApplicationContext wac) {
+ this.mockMvc = MockMvcTester.from(wac);
}
@Nested
@@ -126,8 +125,8 @@ public class AssertableMockMvcIntegrationTests {
assertThat(performWithCookie(cookie, get("/greet"))).cookies().hasValue("test", "value");
}
- private AssertableMvcResult performWithCookie(Cookie cookie, MockHttpServletRequestBuilder request) {
- AssertableMockMvc mockMvc = AssertableMockMvc.of(List.of(new TestController()), builder -> builder.addInterceptors(
+ private MvcTestResult performWithCookie(Cookie cookie, MockHttpServletRequestBuilder request) {
+ MockMvcTester mockMvc = MockMvcTester.of(List.of(new TestController()), builder -> builder.addInterceptors(
new HandlerInterceptor() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) {
@@ -139,16 +138,6 @@ public class AssertableMockMvcIntegrationTests {
}
}
- @Nested
- class ContentTypeTests {
-
- @Test
- void contentType() {
- assertThat(perform(get("/greet"))).contentType().isCompatibleWith("text/plain");
- }
-
- }
-
@Nested
class StatusTests {
@@ -169,8 +158,8 @@ public class AssertableMockMvcIntegrationTests {
@Test
void shouldAssertHeader() {
- assertThat(perform(get("/greet"))).headers()
- .hasValue("Content-Type", "text/plain;charset=ISO-8859-1");
+ assertThat(perform(get("/greet")))
+ .hasHeader("Content-Type", "text/plain;charset=ISO-8859-1");
}
@Test
@@ -250,19 +239,19 @@ public class AssertableMockMvcIntegrationTests {
@Test
void jsonPathContent() {
- assertThat(perform(get("/message"))).body().jsonPath()
+ assertThat(perform(get("/message"))).bodyJson()
.extractingPath("$.message").asString().isEqualTo("hello");
}
@Test
void jsonContentCanLoadResourceFromClasspath() {
- assertThat(perform(get("/message"))).body().json().isLenientlyEqualTo(
- new ClassPathResource("message.json", AssertableMockMvcIntegrationTests.class));
+ assertThat(perform(get("/message"))).bodyJson().isLenientlyEqualTo(
+ new ClassPathResource("message.json", MockMvcTesterIntegrationTests.class));
}
@Test
void jsonContentUsingResourceLoaderClass() {
- assertThat(perform(get("/message"))).body().json(AssertableMockMvcIntegrationTests.class)
+ assertThat(perform(get("/message"))).bodyJson().withResourceLoadClass(MockMvcTesterIntegrationTests.class)
.isLenientlyEqualTo("message.json");
}
@@ -416,8 +405,8 @@ public class AssertableMockMvcIntegrationTests {
}
- private void testAssertionFailureWithUnresolvableException(Consumer assertions) {
- AssertableMvcResult result = perform(get("/error/1"));
+ private void testAssertionFailureWithUnresolvableException(Consumer assertions) {
+ MvcTestResult result = perform(get("/error/1"));
assertThatExceptionOfType(AssertionError.class)
.isThrownBy(() -> assertions.accept(result))
.withMessageContainingAll("Request has failed unexpectedly:",
@@ -441,7 +430,7 @@ public class AssertableMockMvcIntegrationTests {
@Test
void satisfiesAllowsAdditionalAssertions() {
assertThat(this.mockMvc.perform(get("/greet"))).satisfies(result -> {
- assertThat(result).isInstanceOf(MvcResult.class);
+ assertThat(result).isInstanceOf(MvcTestResult.class);
assertThat(result).hasStatusOk();
});
}
@@ -467,7 +456,7 @@ public class AssertableMockMvcIntegrationTests {
}
- private AssertableMvcResult perform(MockHttpServletRequestBuilder builder) {
+ private MvcTestResult perform(MockHttpServletRequestBuilder builder) {
return this.mockMvc.perform(builder);
}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AssertableMockMvcTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterTests.java
similarity index 74%
rename from spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AssertableMockMvcTests.java
rename to spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterTests.java
index c86ae122d7..a8f17e6cf6 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/AssertableMockMvcTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterTests.java
@@ -31,7 +31,7 @@ import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.web.MockServletContext;
-import org.springframework.test.json.JsonPathAssert;
+import org.springframework.test.json.AbstractJsonContentAssert;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
@@ -48,11 +48,11 @@ import static org.springframework.test.web.servlet.request.MockMvcRequestBuilder
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
/**
- * Tests for {@link AssertableMockMvc}.
+ * Tests for {@link MockMvcTester}.
*
* @author Stephane Nicoll
*/
-class AssertableMockMvcTests {
+class MockMvcTesterTests {
private static final MappingJackson2HttpMessageConverter jsonHttpMessageConverter =
new MappingJackson2HttpMessageConverter(new ObjectMapper());
@@ -60,56 +60,56 @@ class AssertableMockMvcTests {
@Test
void createShouldRejectNullMockMvc() {
- assertThatIllegalArgumentException().isThrownBy(() -> AssertableMockMvc.create(null));
+ assertThatIllegalArgumentException().isThrownBy(() -> MockMvcTester.create(null));
}
@Test
void createWithExistingWebApplicationContext() {
try (GenericWebApplicationContext wac = create(WebConfiguration.class)) {
- AssertableMockMvc mockMvc = AssertableMockMvc.from(wac);
- assertThat(mockMvc.perform(post("/increase"))).body().isEqualTo("counter 41");
- assertThat(mockMvc.perform(post("/increase"))).body().isEqualTo("counter 42");
+ MockMvcTester mockMvc = MockMvcTester.from(wac);
+ assertThat(mockMvc.perform(post("/increase"))).hasBodyTextEqualTo("counter 41");
+ assertThat(mockMvc.perform(post("/increase"))).hasBodyTextEqualTo("counter 42");
}
}
@Test
void createWithControllerClassShouldInstantiateControllers() {
- AssertableMockMvc mockMvc = AssertableMockMvc.of(HelloController.class, CounterController.class);
- assertThat(mockMvc.perform(get("/hello"))).body().isEqualTo("Hello World");
- assertThat(mockMvc.perform(post("/increase"))).body().isEqualTo("counter 1");
- assertThat(mockMvc.perform(post("/increase"))).body().isEqualTo("counter 2");
+ MockMvcTester mockMvc = MockMvcTester.of(HelloController.class, CounterController.class);
+ assertThat(mockMvc.perform(get("/hello"))).hasBodyTextEqualTo("Hello World");
+ assertThat(mockMvc.perform(post("/increase"))).hasBodyTextEqualTo("counter 1");
+ assertThat(mockMvc.perform(post("/increase"))).hasBodyTextEqualTo("counter 2");
}
@Test
void createWithControllersShouldUseThemAsIs() {
- AssertableMockMvc mockMvc = AssertableMockMvc.of(new HelloController(),
+ MockMvcTester mockMvc = MockMvcTester.of(new HelloController(),
new CounterController(new AtomicInteger(41)));
- assertThat(mockMvc.perform(get("/hello"))).body().isEqualTo("Hello World");
- assertThat(mockMvc.perform(post("/increase"))).body().isEqualTo("counter 42");
- assertThat(mockMvc.perform(post("/increase"))).body().isEqualTo("counter 43");
+ assertThat(mockMvc.perform(get("/hello"))).hasBodyTextEqualTo("Hello World");
+ assertThat(mockMvc.perform(post("/increase"))).hasBodyTextEqualTo("counter 42");
+ assertThat(mockMvc.perform(post("/increase"))).hasBodyTextEqualTo("counter 43");
}
@Test
void createWithControllerAndCustomizations() {
- AssertableMockMvc mockMvc = AssertableMockMvc.of(List.of(new HelloController()), builder ->
+ MockMvcTester mockMvc = MockMvcTester.of(List.of(new HelloController()), builder ->
builder.defaultRequest(get("/hello").accept(MediaType.APPLICATION_JSON)).build());
assertThat(mockMvc.perform(get("/hello"))).hasStatus(HttpStatus.NOT_ACCEPTABLE);
}
@Test
void createWithControllersHasNoHttpMessageConverter() {
- AssertableMockMvc mockMvc = AssertableMockMvc.of(new HelloController());
- JsonPathAssert jsonPathAssert = assertThat(mockMvc.perform(get("/json"))).hasStatusOk().body().jsonPath();
+ MockMvcTester mockMvc = MockMvcTester.of(new HelloController());
+ AbstractJsonContentAssert> jsonContentAssert = assertThat(mockMvc.perform(get("/json"))).hasStatusOk().bodyJson();
assertThatIllegalStateException()
- .isThrownBy(() -> jsonPathAssert.extractingPath("$").convertTo(Message.class))
+ .isThrownBy(() -> jsonContentAssert.extractingPath("$").convertTo(Message.class))
.withMessageContaining("No JSON message converter available");
}
@Test
void createWithControllerCanConfigureHttpMessageConverters() {
- AssertableMockMvc mockMvc = AssertableMockMvc.of(HelloController.class)
+ MockMvcTester mockMvc = MockMvcTester.of(HelloController.class)
.withHttpMessageConverters(List.of(jsonHttpMessageConverter));
- assertThat(mockMvc.perform(get("/json"))).hasStatusOk().body().jsonPath()
+ assertThat(mockMvc.perform(get("/json"))).hasStatusOk().bodyJson()
.extractingPath("$").convertTo(Message.class).satisfies(message -> {
assertThat(message.message()).isEqualTo("Hello World");
assertThat(message.counter()).isEqualTo(42);
@@ -120,9 +120,9 @@ class AssertableMockMvcTests {
@SuppressWarnings("unchecked")
void withHttpMessageConverterDetectsJsonConverter() {
MappingJackson2HttpMessageConverter converter = spy(jsonHttpMessageConverter);
- AssertableMockMvc mockMvc = AssertableMockMvc.of(HelloController.class)
+ MockMvcTester mockMvc = MockMvcTester.of(HelloController.class)
.withHttpMessageConverters(List.of(mock(), mock(), converter));
- assertThat(mockMvc.perform(get("/json"))).hasStatusOk().body().jsonPath()
+ assertThat(mockMvc.perform(get("/json"))).hasStatusOk().bodyJson()
.extractingPath("$").convertTo(Message.class).satisfies(message -> {
assertThat(message.message()).isEqualTo("Hello World");
assertThat(message.counter()).isEqualTo(42);
@@ -132,11 +132,11 @@ class AssertableMockMvcTests {
@Test
void performWithUnresolvedExceptionSetsException() {
- AssertableMockMvc mockMvc = AssertableMockMvc.of(HelloController.class);
- AssertableMvcResult result = mockMvc.perform(get("/error"));
+ MockMvcTester mockMvc = MockMvcTester.of(HelloController.class);
+ MvcTestResult result = mockMvc.perform(get("/error"));
assertThat(result.getUnresolvedException()).isInstanceOf(ServletException.class)
.cause().isInstanceOf(IllegalStateException.class).hasMessage("Expected");
- assertThat(result).hasFieldOrPropertyWithValue("target", null);
+ assertThat(result).hasFieldOrPropertyWithValue("mvcResult", null);
}
private GenericWebApplicationContext create(Class>... classes) {
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/ResponseBodyAssertTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/ResponseBodyAssertTests.java
deleted file mode 100644
index 0284636c3d..0000000000
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/ResponseBodyAssertTests.java
+++ /dev/null
@@ -1,88 +0,0 @@
-/*
- * Copyright 2002-2024 the original author or authors.
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * https://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package org.springframework.test.web.servlet.assertj;
-
-
-import java.io.UnsupportedEncodingException;
-import java.nio.charset.Charset;
-import java.nio.charset.StandardCharsets;
-
-import org.assertj.core.api.AssertProvider;
-import org.junit.jupiter.api.Test;
-
-import org.springframework.mock.web.MockHttpServletResponse;
-import org.springframework.test.json.JsonContent;
-
-import static org.assertj.core.api.Assertions.assertThat;
-
-/**
- * Tests for {@link ResponseBodyAssert}.
- *
- * @author Brian Clozel
- * @author Stephane Nicoll
- */
-class ResponseBodyAssertTests {
-
- @Test
- void isEqualToWithByteArray() {
- MockHttpServletResponse response = createResponse("hello");
- response.setCharacterEncoding(StandardCharsets.UTF_8.name());
- assertThat(fromResponse(response)).isEqualTo("hello".getBytes(StandardCharsets.UTF_8));
- }
-
- @Test
- void isEqualToWithString() {
- MockHttpServletResponse response = createResponse("hello");
- assertThat(fromResponse(response)).isEqualTo("hello");
- }
-
- @Test
- void jsonPathWithJsonResponseShouldPass() {
- MockHttpServletResponse response = createResponse("{\"message\": \"hello\"}");
- assertThat(fromResponse(response)).jsonPath().extractingPath("$.message").isEqualTo("hello");
- }
-
- @Test
- void jsonPathWithJsonCompatibleResponseShouldPass() {
- MockHttpServletResponse response = createResponse("{\"albumById\": {\"name\": \"Greatest hits\"}}");
- assertThat(fromResponse(response)).jsonPath()
- .extractingPath("$.albumById.name").isEqualTo("Greatest hits");
- }
-
- @Test
- void jsonCanLoadResourceRelativeToClass() {
- MockHttpServletResponse response = createResponse("{ \"name\" : \"Spring\", \"age\" : 123 }");
- // See org/springframework/test/json/example.json
- assertThat(fromResponse(response)).json(JsonContent.class).isLenientlyEqualTo("example.json");
- }
-
- private MockHttpServletResponse createResponse(String body) {
- try {
- MockHttpServletResponse response = new MockHttpServletResponse();
- response.getWriter().print(body);
- return response;
- }
- catch (UnsupportedEncodingException ex) {
- throw new IllegalStateException(ex);
- }
- }
-
- private AssertProvider fromResponse(MockHttpServletResponse response) {
- return () -> new ResponseBodyAssert(response.getContentAsByteArray(), Charset.forName(response.getCharacterEncoding()), null);
- }
-
-}