Add support for JSON assertions using JSON path
This commit moves JSON path AssertJ support from Spring Boot. See gh-21178 Co-authored-by: Brian Clozel <brian.clozel@broadcom.com>
This commit is contained in:
@@ -0,0 +1,235 @@
|
||||
/*
|
||||
* 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.lang.reflect.Array;
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.assertj.core.api.AbstractBooleanAssert;
|
||||
import org.assertj.core.api.AbstractMapAssert;
|
||||
import org.assertj.core.api.AbstractObjectAssert;
|
||||
import org.assertj.core.api.AbstractStringAssert;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.api.ObjectArrayAssert;
|
||||
import org.assertj.core.error.BasicErrorMessageFactory;
|
||||
import org.assertj.core.internal.Failures;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.http.HttpInputMessage;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.mock.http.MockHttpInputMessage;
|
||||
import org.springframework.mock.http.MockHttpOutputMessage;
|
||||
import org.springframework.util.ObjectUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
/**
|
||||
* Base AssertJ {@link org.assertj.core.api.Assert assertions} that can be
|
||||
* applied to a JSON value. In JSON, values must be one of the following data
|
||||
* types:
|
||||
* <ul>
|
||||
* <li>a {@linkplain #asString() string}</li>
|
||||
* <li>a {@linkplain #asNumber() number}</li>
|
||||
* <li>a {@linkplain #asBoolean() boolean}</li>
|
||||
* <li>an {@linkplain #asArray() array}</li>
|
||||
* <li>an {@linkplain #asMap() object} (JSON object)</li>
|
||||
* <li>{@linkplain #isNull() null}</li>
|
||||
* </ul>
|
||||
* This base class offers direct access for each of those types as well as a
|
||||
* conversion methods based on an optional {@link GenericHttpMessageConverter}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.2
|
||||
* @param <SELF> the type of assertions
|
||||
*/
|
||||
public abstract class AbstractJsonValueAssert<SELF extends AbstractJsonValueAssert<SELF>>
|
||||
extends AbstractObjectAssert<SELF, Object> {
|
||||
|
||||
private final Failures failures = Failures.instance();
|
||||
|
||||
@Nullable
|
||||
private final GenericHttpMessageConverter<Object> httpMessageConverter;
|
||||
|
||||
|
||||
protected AbstractJsonValueAssert(@Nullable Object actual, Class<?> selfType,
|
||||
@Nullable GenericHttpMessageConverter<Object> httpMessageConverter) {
|
||||
super(actual, selfType);
|
||||
this.httpMessageConverter = httpMessageConverter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is a non-{@code null} {@link String}
|
||||
* and return a new {@linkplain AbstractStringAssert assertion} object that
|
||||
* provides dedicated {@code String} assertions for it.
|
||||
*/
|
||||
@Override
|
||||
public AbstractStringAssert<?> asString() {
|
||||
return Assertions.assertThat(castTo(String.class, "a string"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is a non-{@code null} {@link Number},
|
||||
* usually an {@link Integer} or {@link Double} and return a new
|
||||
* {@linkplain AbstractObjectAssert assertion} object for it.
|
||||
*/
|
||||
public AbstractObjectAssert<?, Number> asNumber() {
|
||||
return Assertions.assertThat(castTo(Number.class, "a number"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is a non-{@code null} {@link Boolean}
|
||||
* and return a new {@linkplain AbstractBooleanAssert assertion} object
|
||||
* that provides dedicated {@code Boolean} assertions for it.
|
||||
*/
|
||||
public AbstractBooleanAssert<?> asBoolean() {
|
||||
return Assertions.assertThat(castTo(Boolean.class, "a boolean"));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is a non-{@code null} {@link Array}
|
||||
* and return a new {@linkplain ObjectArrayAssert assertion} object
|
||||
* that provides dedicated {@code Array} assertions for it.
|
||||
*/
|
||||
public ObjectArrayAssert<Object> asArray() {
|
||||
List<?> list = castTo(List.class, "an array");
|
||||
Object[] array = list.toArray(new Object[0]);
|
||||
return Assertions.assertThat(array);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is a non-{@code null} JSON object and
|
||||
* return a new {@linkplain AbstractMapAssert assertion} object that
|
||||
* provides dedicated assertions on individual elements of the
|
||||
* object. The returned map assertion object uses the attribute name as the
|
||||
* key, and the value can itself be any of the valid JSON values.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public AbstractMapAssert<?, Map<String, Object>, String, Object> asMap() {
|
||||
return Assertions.assertThat(castTo(Map.class, "a map"));
|
||||
}
|
||||
|
||||
private <T> T castTo(Class<T> expectedType, String description) {
|
||||
if (this.actual == null) {
|
||||
throw valueProcessingFailed("To be %s%n".formatted(description));
|
||||
}
|
||||
if (!expectedType.isInstance(this.actual)) {
|
||||
throw valueProcessingFailed("To be %s%nBut was:%n %s%n".formatted(description, this.actual.getClass().getName()));
|
||||
}
|
||||
return expectedType.cast(this.actual);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value can be converted to an instance of the
|
||||
* given {@code target} and produce a new {@linkplain AbstractObjectAssert
|
||||
* assertion} object narrowed to that type.
|
||||
* @param target the {@linkplain Class type} to convert the actual value to
|
||||
*/
|
||||
public <T> AbstractObjectAssert<?, T> convertTo(Class<T> target) {
|
||||
isNotNull();
|
||||
T value = convertToTargetType(target);
|
||||
return Assertions.assertThat(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value can be converted to an instance of the
|
||||
* given {@code target} and produce a new {@linkplain AbstractObjectAssert
|
||||
* assertion} object narrowed to that type.
|
||||
* @param target the {@linkplain ParameterizedTypeReference parameterized
|
||||
* type} to convert the actual value to
|
||||
*/
|
||||
public <T> AbstractObjectAssert<?, T> convertTo(ParameterizedTypeReference<T> target) {
|
||||
isNotNull();
|
||||
T value = convertToTargetType(target.getType());
|
||||
return Assertions.assertThat(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is empty, that is a {@code null} scalar
|
||||
* value or an empty list or map. Can also be used when the path is using a
|
||||
* filter operator to validate that it dit not match.
|
||||
*/
|
||||
public SELF isEmpty() {
|
||||
if (!ObjectUtils.isEmpty(this.actual)) {
|
||||
throw valueProcessingFailed("To be empty");
|
||||
}
|
||||
return this.myself;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual value is not empty, that is a non-{@code null}
|
||||
* scalar value or a non-empty list or map. Can also be used when the path is
|
||||
* using a filter operator to validate that it dit match at least one
|
||||
* element.
|
||||
*/
|
||||
public SELF isNotEmpty() {
|
||||
if (ObjectUtils.isEmpty(this.actual)) {
|
||||
throw valueProcessingFailed("To not be empty");
|
||||
}
|
||||
return this.myself;
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T convertToTargetType(Type targetType) {
|
||||
if (this.httpMessageConverter == null) {
|
||||
throw new IllegalStateException(
|
||||
"No JSON message converter available to convert %s".formatted(actualToString()));
|
||||
}
|
||||
try {
|
||||
MockHttpOutputMessage outputMessage = new MockHttpOutputMessage();
|
||||
this.httpMessageConverter.write(this.actual, ResolvableType.forInstance(this.actual).getType(),
|
||||
MediaType.APPLICATION_JSON, outputMessage);
|
||||
return (T) this.httpMessageConverter.read(targetType, getClass(),
|
||||
fromHttpOutputMessage(outputMessage));
|
||||
}
|
||||
catch (Exception ex) {
|
||||
throw valueProcessingFailed("To convert successfully to:%n %s%nBut it failed:%n %s%n"
|
||||
.formatted(targetType.getTypeName(), ex.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
private HttpInputMessage fromHttpOutputMessage(MockHttpOutputMessage message) {
|
||||
MockHttpInputMessage inputMessage = new MockHttpInputMessage(message.getBodyAsBytes());
|
||||
inputMessage.getHeaders().addAll(message.getHeaders());
|
||||
return inputMessage;
|
||||
}
|
||||
|
||||
protected String getExpectedErrorMessagePrefix() {
|
||||
return "Expected:";
|
||||
}
|
||||
|
||||
private AssertionError valueProcessingFailed(String errorMessage) {
|
||||
throw this.failures.failure(this.info, new ValueProcessingFailed(
|
||||
getExpectedErrorMessagePrefix(), actualToString(), errorMessage));
|
||||
}
|
||||
|
||||
private String actualToString() {
|
||||
return ObjectUtils.nullSafeToString(StringUtils.quoteIfString(this.actual));
|
||||
}
|
||||
|
||||
private static final class ValueProcessingFailed extends BasicErrorMessageFactory {
|
||||
|
||||
private ValueProcessingFailed(String prefix, String actualToString, String errorMessage) {
|
||||
super("%n%s%n %s%n%s".formatted(prefix, actualToString, errorMessage));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,165 @@
|
||||
/*
|
||||
* 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%nTo not match JSON path:%n %s%n", actual, path);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
/*
|
||||
* 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 com.jayway.jsonpath.JsonPath;
|
||||
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied
|
||||
* to a JSON value produced by evaluating a {@linkplain JsonPath JSON path}
|
||||
* expression.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
* @since 6.2
|
||||
*/
|
||||
public class JsonPathValueAssert
|
||||
extends AbstractJsonValueAssert<JsonPathValueAssert> {
|
||||
|
||||
private final String expression;
|
||||
|
||||
|
||||
JsonPathValueAssert(@Nullable Object actual, String expression,
|
||||
@Nullable GenericHttpMessageConverter<Object> httpMessageConverter) {
|
||||
super(actual, JsonPathValueAssert.class, httpMessageConverter);
|
||||
this.expression = expression;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getExpectedErrorMessagePrefix() {
|
||||
return "Expected value at JSON path \"%s\":".formatted(this.expression);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,322 @@
|
||||
/*
|
||||
* 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:",
|
||||
"To not 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);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,333 @@
|
||||
/*
|
||||
* 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.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||
import org.assertj.core.api.AssertProvider;
|
||||
import org.assertj.core.api.InstanceOfAssertFactories;
|
||||
import org.assertj.core.data.Offset;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import org.springframework.core.ParameterizedTypeReference;
|
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
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 JsonPathValueAssert}.
|
||||
*
|
||||
* @author Stephane Nicoll
|
||||
*/
|
||||
class JsonPathValueAssertTests {
|
||||
|
||||
@Nested
|
||||
class AsStringTests {
|
||||
|
||||
@Test
|
||||
void asStringWithStringValue() {
|
||||
assertThat(forValue("test")).asString().isEqualTo("test");
|
||||
}
|
||||
|
||||
@Test
|
||||
void asStringWithEmptyValue() {
|
||||
assertThat(forValue("")).asString().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void asStringWithNonStringFails() {
|
||||
int value = 123;
|
||||
AssertProvider<JsonPathValueAssert> actual = forValue(value);
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertThat(actual).asString().isEqualTo("123"))
|
||||
.satisfies(hasFailedToBeOfType(value, "a string"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void asStringWithNullFails() {
|
||||
AssertProvider<JsonPathValueAssert> actual = forValue(null);
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertThat(actual).asString().isEqualTo("null"))
|
||||
.satisfies(hasFailedToBeOfTypeWhenNull("a string"));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class AsNumberTests {
|
||||
|
||||
@Test
|
||||
void asNumberWithIntegerValue() {
|
||||
assertThat(forValue(123)).asNumber().isEqualTo(123);
|
||||
}
|
||||
|
||||
@Test
|
||||
void asNumberWithDoubleValue() {
|
||||
assertThat(forValue(3.1415926)).asNumber()
|
||||
.asInstanceOf(InstanceOfAssertFactories.DOUBLE)
|
||||
.isEqualTo(3.14, Offset.offset(0.01));
|
||||
}
|
||||
|
||||
@Test
|
||||
void asNumberWithNonNumberFails() {
|
||||
String value = "123";
|
||||
AssertProvider<JsonPathValueAssert> actual = forValue(value);
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertThat(actual).asNumber().isEqualTo(123))
|
||||
.satisfies(hasFailedToBeOfType(value, "a number"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void asNumberWithNullFails() {
|
||||
AssertProvider<JsonPathValueAssert> actual = forValue(null);
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertThat(actual).asNumber().isEqualTo(0))
|
||||
.satisfies(hasFailedToBeOfTypeWhenNull("a number"));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class AsBooleanTests {
|
||||
|
||||
@Test
|
||||
void asBooleanWithBooleanPrimitiveValue() {
|
||||
assertThat(forValue(true)).asBoolean().isEqualTo(true);
|
||||
}
|
||||
|
||||
@Test
|
||||
void asBooleanWithBooleanWrapperValue() {
|
||||
assertThat(forValue(Boolean.FALSE)).asBoolean().isEqualTo(false);
|
||||
}
|
||||
|
||||
@Test
|
||||
void asBooleanWithNonBooleanFails() {
|
||||
String value = "false";
|
||||
AssertProvider<JsonPathValueAssert> actual = forValue(value);
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertThat(actual).asBoolean().isEqualTo(false))
|
||||
.satisfies(hasFailedToBeOfType(value, "a boolean"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void asBooleanWithNullFails() {
|
||||
AssertProvider<JsonPathValueAssert> actual = forValue(null);
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertThat(actual).asBoolean().isEqualTo(false))
|
||||
.satisfies(hasFailedToBeOfTypeWhenNull("a boolean"));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class AsArrayTests { // json path uses List for arrays
|
||||
|
||||
@Test
|
||||
void asArrayWithStringValues() {
|
||||
assertThat(forValue(List.of("a", "b", "c"))).asArray().contains("a", "c");
|
||||
}
|
||||
|
||||
@Test
|
||||
void asArrayWithEmptyArray() {
|
||||
assertThat(forValue(Collections.emptyList())).asArray().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void asArrayWithNonArrayFails() {
|
||||
String value = "test";
|
||||
AssertProvider<JsonPathValueAssert> actual = forValue(value);
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertThat(actual).asArray().contains("t"))
|
||||
.satisfies(hasFailedToBeOfType(value, "an array"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void asArrayWithNullFails() {
|
||||
AssertProvider<JsonPathValueAssert> actual = forValue(null);
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertThat(actual).asArray().isEqualTo(false))
|
||||
.satisfies(hasFailedToBeOfTypeWhenNull("an array"));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class AsMapTests {
|
||||
|
||||
@Test
|
||||
void asMapWithMapValue() {
|
||||
assertThat(forValue(Map.of("zero", 0, "one", 1))).asMap().containsKeys("zero", "one")
|
||||
.containsValues(0, 1);
|
||||
}
|
||||
|
||||
@Test
|
||||
void asArrayWithEmptyMap() {
|
||||
assertThat(forValue(Collections.emptyMap())).asMap().isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void asMapWithNonMapFails() {
|
||||
List<String> value = List.of("a", "b");
|
||||
AssertProvider<JsonPathValueAssert> actual = forValue(value);
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertThat(actual).asMap().containsKey("a"))
|
||||
.satisfies(hasFailedToBeOfType(value, "a map"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void asMapWithNullFails() {
|
||||
AssertProvider<JsonPathValueAssert> actual = forValue(null);
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertThat(actual).asMap().isEmpty())
|
||||
.satisfies(hasFailedToBeOfTypeWhenNull("a map"));
|
||||
}
|
||||
}
|
||||
|
||||
@Nested
|
||||
class ConvertToTests {
|
||||
|
||||
private static final MappingJackson2HttpMessageConverter jsonHttpMessageConverter =
|
||||
new MappingJackson2HttpMessageConverter(new ObjectMapper());
|
||||
|
||||
@Test
|
||||
void convertToWithoutHttpMessageConverter() {
|
||||
AssertProvider<JsonPathValueAssert> actual = () -> new JsonPathValueAssert("123", "$.test", null);
|
||||
assertThatIllegalStateException().isThrownBy(() -> assertThat(actual).convertTo(Integer.class))
|
||||
.withMessage("No JSON message converter available to convert '123'");
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertObjectToPojo() {
|
||||
assertThat(forValue(Map.of("id", 1234, "name", "John", "active", true))).convertTo(User.class)
|
||||
.satisfies(user -> {
|
||||
assertThat(user.id).isEqualTo(1234);
|
||||
assertThat(user.name).isEqualTo("John");
|
||||
assertThat(user.active).isTrue();
|
||||
});
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertArrayToListOfPojo() {
|
||||
Map<?, ?> user1 = Map.of("id", 1234, "name", "John", "active", true);
|
||||
Map<?, ?> user2 = Map.of("id", 5678, "name", "Sarah", "active", false);
|
||||
Map<?, ?> user3 = Map.of("id", 9012, "name", "Sophia", "active", true);
|
||||
assertThat(forValue(List.of(user1, user2, user3)))
|
||||
.convertTo(new ParameterizedTypeReference<List<User>>() {})
|
||||
.satisfies(users -> assertThat(users).hasSize(3).extracting("name")
|
||||
.containsExactly("John", "Sarah", "Sophia"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void convertObjectToPojoWithMissingMandatoryField() {
|
||||
Map<?, ?> value = Map.of("firstName", "John");
|
||||
AssertProvider<JsonPathValueAssert> actual = forValue(value);
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertThat(actual).convertTo(User.class))
|
||||
.satisfies(hasFailedToConvertToType(value, User.class))
|
||||
.withMessageContaining("firstName");
|
||||
}
|
||||
|
||||
|
||||
private AssertProvider<JsonPathValueAssert> forValue(@Nullable Object actual) {
|
||||
return () -> new JsonPathValueAssert(actual, "$.test", jsonHttpMessageConverter);
|
||||
}
|
||||
|
||||
|
||||
private record User(long id, String name, boolean active) {}
|
||||
|
||||
}
|
||||
|
||||
@Nested
|
||||
class EmptyNotEmptyTests {
|
||||
|
||||
@Test
|
||||
void isEmptyWithEmptyString() {
|
||||
assertThat(forValue("")).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmptyWithNull() {
|
||||
assertThat(forValue(null)).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmptyWithEmptyArray() {
|
||||
assertThat(forValue(Collections.emptyList())).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmptyWithEmptyObject() {
|
||||
assertThat(forValue(Collections.emptyMap())).isEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isEmptyWithWhitespace() {
|
||||
AssertProvider<JsonPathValueAssert> actual = forValue(" ");
|
||||
assertThatExceptionOfType(AssertionError.class)
|
||||
.isThrownBy(() -> assertThat(actual).isEmpty())
|
||||
.satisfies(hasFailedEmptyCheck(" "));
|
||||
}
|
||||
|
||||
@Test
|
||||
void isNotEmptyWithString() {
|
||||
assertThat(forValue("test")).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isNotEmptyWithArray() {
|
||||
assertThat(forValue(List.of("test"))).isNotEmpty();
|
||||
}
|
||||
|
||||
@Test
|
||||
void isNotEmptyWithObject() {
|
||||
assertThat(forValue(Map.of("test", "value"))).isNotEmpty();
|
||||
}
|
||||
|
||||
private Consumer<AssertionError> hasFailedEmptyCheck(Object actual) {
|
||||
return error -> assertThat(error.getMessage()).containsSubsequence("Expected value at JSON path \"$.test\":",
|
||||
"" + StringUtils.quoteIfString(actual), "To be empty");
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private Consumer<AssertionError> hasFailedToBeOfType(Object actual, String expectedDescription) {
|
||||
return error -> assertThat(error.getMessage()).containsSubsequence("Expected value at JSON path \"$.test\":",
|
||||
"" + StringUtils.quoteIfString(actual), "To be " + expectedDescription, "But was:", actual.getClass().getName());
|
||||
}
|
||||
|
||||
private Consumer<AssertionError> hasFailedToBeOfTypeWhenNull(String expectedDescription) {
|
||||
return error -> assertThat(error.getMessage()).containsSubsequence("Expected value at JSON path \"$.test\":", "null",
|
||||
"To be " + expectedDescription);
|
||||
}
|
||||
|
||||
private Consumer<AssertionError> hasFailedToConvertToType(Object actual, Class<?> targetType) {
|
||||
return error -> assertThat(error.getMessage()).containsSubsequence("Expected value at JSON path \"$.test\":",
|
||||
"" + StringUtils.quoteIfString(actual), "To convert successfully to:", targetType.getTypeName(), "But it failed:");
|
||||
}
|
||||
|
||||
|
||||
|
||||
private AssertProvider<JsonPathValueAssert> forValue(@Nullable Object actual) {
|
||||
return () -> new JsonPathValueAssert(actual, "$.test", null);
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user