Merge JSON assertions in a single Assert type

This commit merges the JSONCompare and JsonPath support in a single
assert object. The rationale for the previous situation was that
JsonPath is optional but merging the assertions methods do not make
it mandatory as the usage if JsonPath is triggered only if it is
actually used.

See gh-32712
This commit is contained in:
Stéphane Nicoll 2024-05-06 11:08:02 +02:00
parent 645556a28c
commit 24cc77655f
9 changed files with 1320 additions and 1323 deletions

View File

@ -0,0 +1,501 @@
/*
* 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.
*
* <p>Support evaluating {@linkplain JsonPath JSON path} expressions and
* extracting a part of the document for further {@linkplain JsonPathValueAssert
* assertions} on the value.
*
* <p>Also support comparing the JSON document against a target, using
* {@linkplain JSONCompare JSON Assert}.
*
* @author Stephane Nicoll
* @author Phillip Webb
* @author Andy Wilkinson
* @author Diego Berrueta
* @author Camille Vienot
* @since 6.2
* @param <SELF> the type of assertions
*/
public abstract class AbstractJsonContentAssert<SELF extends AbstractJsonContentAssert<SELF>>
extends AbstractStringAssert<SELF> {
private static final Failures failures = Failures.instance();
@Nullable
private final GenericHttpMessageConverter<Object> jsonMessageConverter;
private final JsonLoader jsonLoader;
/**
* Create an assert for the given JSON document.
* <p>Path can be converted to a value object using the given
* {@linkplain GenericHttpMessageConverter json message converter}.
* <p>Resources to match can be loaded relative to the given
* {@code resourceLoadClass}. If not specified, resources must always be
* absolute. A specific {@link Charset} can be provided if {@code UTF-8} is
* not suitable.
* @param json the JSON document to assert
* @param jsonMessageConverter the converter to use
* @param resourceLoadClass the class used to load resources
* @param charset the charset of the JSON resources
* @param selfType the implementation type of this assert
*/
protected AbstractJsonContentAssert(@Nullable String json,
@Nullable GenericHttpMessageConverter<Object> jsonMessageConverter, @Nullable Class<?> resourceLoadClass,
@Nullable Charset charset, Class<?> selfType) {
super(json, selfType);
this.jsonMessageConverter = jsonMessageConverter;
this.jsonLoader = new JsonLoader(resourceLoadClass, charset);
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<AssertProvider<JsonPathValueAssert>> 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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @param expected a resource containing the expected JSON
*/
public SELF 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<String, String, JSONCompareResult> 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);
}
}
}
}

View File

@ -55,7 +55,7 @@ public final class JsonContent implements AssertProvider<JsonContentAssert> {
*/
@Override
public JsonContentAssert assertThat() {
return new JsonContentAssert(this.json, this.resourceLoadClass, null);
return new JsonContentAssert(this.json, null, this.resourceLoadClass, null);
}
/**

View File

@ -16,351 +16,36 @@
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<JsonContentAssert, CharSequence> {
private final JsonLoader loader;
public class JsonContentAssert extends AbstractJsonContentAssert<JsonContentAssert> {
/**
* 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
* Create an assert for the given JSON document.
* <p>Path can be converted to a value object using the given
* {@linkplain GenericHttpMessageConverter json message converter}.
* <p>Resources to match can be loaded relative to the given
* {@code resourceLoadClass}. If not specified, resources must always be
* absolute. A specific {@link Charset} can be provided if {@code UTF-8} is
* not suitable.
* @param json the JSON document to assert
* @param jsonMessageConverter the converter to use
* @param resourceLoadClass the class used to load resources
* @param charset the charset of the JSON resources
*/
public JsonContentAssert(@Nullable CharSequence json, @Nullable Class<?> resourceLoadClass,
@Nullable Charset charset) {
public JsonContentAssert(@Nullable String json, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter,
@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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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}.
* <p>The resource abstraction allows to provide several input types:
* <ul>
* <li>a {@code byte} array, using {@link ByteArrayResource}</li>
* <li>a {@code classpath} resource, using {@link ClassPathResource}</li>
* <li>a {@link File} or {@link Path}, using {@link FileSystemResource}</li>
* <li>an {@link InputStream}, using {@link InputStreamResource}</li>
* </ul>
* @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<String, String, JSONCompareResult> 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;
super(json, jsonMessageConverter, resourceLoadClass, charset, JsonContentAssert.class);
}
}

View File

@ -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<JsonPathAssert, CharSequence> {
private static final Failures failures = Failures.instance();
@Nullable
private final GenericHttpMessageConverter<Object> jsonMessageConverter;
public JsonPathAssert(CharSequence json,
@Nullable GenericHttpMessageConverter<Object> 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<AssertProvider<JsonPathValueAssert>> 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);
}
}
}
}

View File

@ -25,8 +25,8 @@ 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.AbstractJsonContentAssert;
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
@ -53,19 +53,20 @@ public class ResponseBodyAssert extends AbstractByteArrayAssert<ResponseBodyAsse
}
/**
* Return a new {@linkplain JsonPathAssert assertion} object that provides
* {@linkplain com.jayway.jsonpath.JsonPath JSON path} assertions on the
* response body.
* Return a new {@linkplain AbstractJsonContentAssert 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);
public AbstractJsonContentAssert<?> jsonPath() {
return new JsonContentAssert(getJson(), this.jsonMessageConverter, null, this.characterEncoding)
.as("JSON body");
}
/**
* 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.
* Return a new {@linkplain AbstractJsonContentAssert 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.
* <p>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.
@ -76,15 +77,15 @@ public class ResponseBodyAssert extends AbstractByteArrayAssert<ResponseBodyAsse
* .isStrictlyEqualToJson("/com/acme/web/person/person-created.json");
* </code></pre>
*/
public JsonContentAssert json() {
public AbstractJsonContentAssert<?> 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.
* Return a new {@linkplain AbstractJsonContentAssert 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.
* <p>Locations for JSON documents can be absolute using a leading slash, or
* relative to the given {@code resourceLoadClass}.
* <p>Example: <pre><code class='java'>
@ -96,8 +97,9 @@ public class ResponseBodyAssert extends AbstractByteArrayAssert<ResponseBodyAsse
* @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);
public AbstractJsonContentAssert<?> json(@Nullable Class<?> resourceLoadClass) {
return new JsonContentAssert(getJson(), this.jsonMessageConverter, resourceLoadClass, this.characterEncoding)
.as("JSON body");
}
/**

View File

@ -0,0 +1,781 @@
/*
* 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.charset.Charset;
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<Arguments> 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<List<Member>>() {})
.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<AbstractJsonContentAssert<?>> forJson(@Nullable String json) {
return () -> new TestJsonContentAssert(json, null, null, null);
}
private AssertProvider<AbstractJsonContentAssert<?>> forJson(@Nullable String json, GenericHttpMessageConverter<Object> jsonHttpMessageConverter) {
return () -> new TestJsonContentAssert(json, jsonHttpMessageConverter, null, null);
}
}
@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<Arguments> 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<Arguments> 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<Arguments> 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);
}
private AssertProvider<AbstractJsonContentAssert<?>> forJson(@Nullable String json) {
return () -> new TestJsonContentAssert(json, null, getClass(), null);
}
}
private Consumer<AssertionError> hasFailedToMatchPath(String expression) {
return error -> assertThat(error.getMessage()).containsSubsequence("Expecting:",
"To match JSON path:", "\"" + expression + "\"");
}
private Consumer<AssertionError> 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<AbstractJsonContentAssert<?>> forJson(@Nullable String json) {
return () -> new TestJsonContentAssert(json, null, null, null);
}
private static class TestJsonContentAssert extends AbstractJsonContentAssert<TestJsonContentAssert> {
public TestJsonContentAssert(@Nullable String json, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter, @Nullable Class<?> resourceLoadClass, @Nullable Charset charset) {
super(json, jsonMessageConverter, resourceLoadClass, charset, TestJsonContentAssert.class);
}
}
}

View File

@ -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<Arguments> 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<Arguments> 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<Arguments> 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<JsonContentAssert> forJson(@Nullable String json) {
return () -> new JsonContentAssert(json, JsonContentAssertTests.class);
}
}

View File

@ -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<Arguments> 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<List<Member>>() {})
.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<AssertionError> hasFailedToMatchPath(String expression) {
return error -> assertThat(error.getMessage()).containsSubsequence("Expecting:",
"To match JSON path:", "\"" + expression + "\"");
}
private Consumer<AssertionError> 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<JsonPathAssert> forJson(String json) {
return forJson(json, null);
}
private AssertProvider<JsonPathAssert> forJson(String json,
@Nullable GenericHttpMessageConverter<Object> jsonHttpMessageConverter) {
return () -> new JsonPathAssert(json, jsonHttpMessageConverter);
}
}

View File

@ -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;
@ -99,9 +99,9 @@ class AssertableMockMvcTests {
@Test
void createWithControllersHasNoHttpMessageConverter() {
AssertableMockMvc mockMvc = AssertableMockMvc.of(new HelloController());
JsonPathAssert jsonPathAssert = assertThat(mockMvc.perform(get("/json"))).hasStatusOk().body().jsonPath();
AbstractJsonContentAssert<?> jsonContentAssert = assertThat(mockMvc.perform(get("/json"))).hasStatusOk().body().jsonPath();
assertThatIllegalStateException()
.isThrownBy(() -> jsonPathAssert.extractingPath("$").convertTo(Message.class))
.isThrownBy(() -> jsonContentAssert.extractingPath("$").convertTo(Message.class))
.withMessageContaining("No JSON message converter available");
}