56 changed files with 7023 additions and 0 deletions
@ -0,0 +1,129 @@
@@ -0,0 +1,129 @@
|
||||
/* |
||||
* 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.http; |
||||
|
||||
import java.time.Instant; |
||||
import java.time.ZoneId; |
||||
import java.time.ZonedDateTime; |
||||
import java.time.temporal.ChronoUnit; |
||||
import java.util.List; |
||||
|
||||
import org.assertj.core.api.AbstractMapAssert; |
||||
import org.assertj.core.api.Assertions; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
|
||||
/** |
||||
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to |
||||
* {@link HttpHeaders}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.2 |
||||
*/ |
||||
public class HttpHeadersAssert extends AbstractMapAssert<HttpHeadersAssert, HttpHeaders, String, List<String>> { |
||||
|
||||
private static final ZoneId GMT = ZoneId.of("GMT"); |
||||
|
||||
|
||||
public HttpHeadersAssert(HttpHeaders actual) { |
||||
super(actual, HttpHeadersAssert.class); |
||||
as("HTTP headers"); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual HTTP headers contain a header with the given |
||||
* {@code name}. |
||||
* @param name the name of an expected HTTP header |
||||
* @see #containsKey |
||||
*/ |
||||
public HttpHeadersAssert containsHeader(String name) { |
||||
return containsKey(name); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual HTTP headers contain the headers with the given |
||||
* {@code names}. |
||||
* @param names the names of expected HTTP headers |
||||
* @see #containsKeys |
||||
*/ |
||||
public HttpHeadersAssert containsHeaders(String... names) { |
||||
return containsKeys(names); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual HTTP headers do not contain a header with the |
||||
* given {@code name}. |
||||
* @param name the name of an HTTP header that should not be present |
||||
* @see #doesNotContainKey |
||||
*/ |
||||
public HttpHeadersAssert doesNotContainsHeader(String name) { |
||||
return doesNotContainKey(name); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual HTTP headers do not contain any of the headers |
||||
* with the given {@code names}. |
||||
* @param names the names of HTTP headers that should not be present |
||||
* @see #doesNotContainKeys |
||||
*/ |
||||
public HttpHeadersAssert doesNotContainsHeaders(String... names) { |
||||
return doesNotContainKeys(names); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual HTTP headers contain a header with the given |
||||
* {@code name} and {@link String} {@code value}. |
||||
* @param name the name of the cookie |
||||
* @param value the expected value of the header |
||||
*/ |
||||
public HttpHeadersAssert hasValue(String name, String value) { |
||||
containsKey(name); |
||||
Assertions.assertThat(this.actual.getFirst(name)) |
||||
.as("check primary value for HTTP header '%s'", name) |
||||
.isEqualTo(value); |
||||
return this.myself; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual HTTP headers contain a header with the given |
||||
* {@code name} and {@link Long} {@code value}. |
||||
* @param name the name of the cookie |
||||
* @param value the expected value of the header |
||||
*/ |
||||
public HttpHeadersAssert hasValue(String name, long value) { |
||||
containsKey(name); |
||||
Assertions.assertThat(this.actual.getFirst(name)) |
||||
.as("check primary long value for HTTP header '%s'", name) |
||||
.asLong().isEqualTo(value); |
||||
return this.myself; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual HTTP headers contain a header with the given |
||||
* {@code name} and {@link Instant} {@code value}. |
||||
* @param name the name of the cookie |
||||
* @param value the expected value of the header |
||||
*/ |
||||
public HttpHeadersAssert hasValue(String name, Instant value) { |
||||
containsKey(name); |
||||
Assertions.assertThat(this.actual.getFirstZonedDateTime(name)) |
||||
.as("check primary date value for HTTP header '%s'", name) |
||||
.isCloseTo(ZonedDateTime.ofInstant(value, GMT), Assertions.within(999, ChronoUnit.MILLIS)); |
||||
return this.myself; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,107 @@
@@ -0,0 +1,107 @@
|
||||
/* |
||||
* 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.http; |
||||
|
||||
import org.assertj.core.api.AbstractObjectAssert; |
||||
import org.assertj.core.api.Assertions; |
||||
import org.assertj.core.error.BasicErrorMessageFactory; |
||||
import org.assertj.core.internal.Failures; |
||||
|
||||
import org.springframework.http.InvalidMediaTypeException; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.StringUtils; |
||||
|
||||
/** |
||||
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied |
||||
* to a {@link MediaType}. |
||||
* |
||||
* @author Brian Clozel |
||||
* @author Stephane Nicoll |
||||
* @since 6.2 |
||||
*/ |
||||
public class MediaTypeAssert extends AbstractObjectAssert<MediaTypeAssert, MediaType> { |
||||
|
||||
public MediaTypeAssert(@Nullable MediaType mediaType) { |
||||
super(mediaType, MediaTypeAssert.class); |
||||
as("Media type"); |
||||
} |
||||
|
||||
public MediaTypeAssert(@Nullable String actual) { |
||||
this(StringUtils.hasText(actual) ? MediaType.parseMediaType(actual) : null); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual media type is equal to the given string |
||||
* representation. |
||||
* @param expected the expected media type |
||||
*/ |
||||
public MediaTypeAssert isEqualTo(String expected) { |
||||
return isEqualTo(parseMediaType(expected)); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual media type is |
||||
* {@linkplain MediaType#isCompatibleWith(MediaType) compatible} with the |
||||
* given one. Example: <pre><code class='java'> |
||||
* // Check that actual is compatible with "application/json"
|
||||
* assertThat(mediaType).isCompatibleWith(MediaType.APPLICATION_JSON); |
||||
* </code></pre> |
||||
* @param mediaType the media type with which to compare |
||||
*/ |
||||
public MediaTypeAssert isCompatibleWith(MediaType mediaType) { |
||||
Assertions.assertThat(this.actual) |
||||
.withFailMessage("Expecting null to be compatible with '%s'", mediaType).isNotNull(); |
||||
Assertions.assertThat(mediaType) |
||||
.withFailMessage("Expecting '%s' to be compatible with null", this.actual).isNotNull(); |
||||
Assertions.assertThat(this.actual.isCompatibleWith(mediaType)) |
||||
.as("check media type '%s' is compatible with '%s'", this.actual.toString(), mediaType.toString()) |
||||
.isTrue(); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual media type is |
||||
* {@linkplain MediaType#isCompatibleWith(MediaType) compatible} with the |
||||
* given one. Example: <pre><code class='java'> |
||||
* // Check that actual is compatible with "text/plain"
|
||||
* assertThat(mediaType).isCompatibleWith("text/plain"); |
||||
* </code></pre> |
||||
* @param mediaType the media type with which to compare |
||||
*/ |
||||
public MediaTypeAssert isCompatibleWith(String mediaType) { |
||||
return isCompatibleWith(parseMediaType(mediaType)); |
||||
} |
||||
|
||||
|
||||
private MediaType parseMediaType(String value) { |
||||
try { |
||||
return MediaType.parseMediaType(value); |
||||
} |
||||
catch (InvalidMediaTypeException ex) { |
||||
throw Failures.instance().failure(this.info, new ShouldBeValidMediaType(value, ex.getMessage())); |
||||
} |
||||
} |
||||
|
||||
private static final class ShouldBeValidMediaType extends BasicErrorMessageFactory { |
||||
|
||||
private ShouldBeValidMediaType(String mediaType, String errorMessage) { |
||||
super("%nExpecting:%n %s%nTo be a valid media type but got:%n %s%n", mediaType, errorMessage); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
/** |
||||
* Test support for HTTP concepts. |
||||
*/ |
||||
@NonNullApi |
||||
@NonNullFields |
||||
package org.springframework.test.http; |
||||
|
||||
import org.springframework.lang.NonNullApi; |
||||
import org.springframework.lang.NonNullFields; |
||||
@ -0,0 +1,235 @@
@@ -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,73 @@
@@ -0,0 +1,73 @@
|
||||
/* |
||||
* 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 org.assertj.core.api.AssertProvider; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* JSON content usually created from a JSON tester. Generally used only to |
||||
* {@link AssertProvider provide} {@link JsonContentAssert} to AssertJ |
||||
* {@code assertThat} calls. |
||||
* |
||||
* @author Phillip Webb |
||||
* @author Diego Berrueta |
||||
* @since 6.2 |
||||
*/ |
||||
public final class JsonContent implements AssertProvider<JsonContentAssert> { |
||||
|
||||
private final String json; |
||||
|
||||
@Nullable |
||||
private final Class<?> resourceLoadClass; |
||||
|
||||
/** |
||||
* Create a new {@link JsonContent} instance. |
||||
* @param json the actual JSON content |
||||
* @param resourceLoadClass the source class used to load resources |
||||
*/ |
||||
JsonContent(String json, @Nullable Class<?> resourceLoadClass) { |
||||
Assert.notNull(json, "JSON must not be null"); |
||||
this.json = json; |
||||
this.resourceLoadClass = resourceLoadClass; |
||||
} |
||||
|
||||
/** |
||||
* Use AssertJ's {@link org.assertj.core.api.Assertions#assertThat assertThat} |
||||
* instead. |
||||
*/ |
||||
@Override |
||||
public JsonContentAssert assertThat() { |
||||
return new JsonContentAssert(this.json, this.resourceLoadClass, null); |
||||
} |
||||
|
||||
/** |
||||
* Return the actual JSON content string. |
||||
* @return the JSON content |
||||
*/ |
||||
public String getJson() { |
||||
return this.json; |
||||
} |
||||
|
||||
@Override |
||||
public String toString() { |
||||
return "JsonContent " + this.json; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,367 @@
@@ -0,0 +1,367 @@
|
||||
/* |
||||
* 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 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.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}. |
||||
* |
||||
* @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; |
||||
|
||||
/** |
||||
* Create a new {@link JsonContentAssert} instance that will load resources |
||||
* relative to the given {@code resourceLoadClass}, using the given |
||||
* {@code charset}. |
||||
* @param json the actual JSON content |
||||
* @param resourceLoadClass the source class used to load resources |
||||
* @param charset the charset of the JSON resources |
||||
*/ |
||||
public JsonContentAssert(@Nullable CharSequence json, @Nullable Class<?> resourceLoadClass, |
||||
@Nullable Charset charset) { |
||||
|
||||
super(json, JsonContentAssert.class); |
||||
this.loader = new JsonLoader(resourceLoadClass, charset); |
||||
} |
||||
|
||||
/** |
||||
* Create a new {@link JsonContentAssert} instance that will load resources |
||||
* relative to the given {@code resourceLoadClass}, using {@code UTF-8}. |
||||
* @param json the actual JSON content |
||||
* @param resourceLoadClass the source 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(); |
||||
result.passed(); |
||||
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; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,74 @@
@@ -0,0 +1,74 @@
|
||||
/* |
||||
* 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.IOException; |
||||
import java.io.InputStream; |
||||
import java.io.InputStreamReader; |
||||
import java.nio.charset.Charset; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
import org.springframework.core.io.ClassPathResource; |
||||
import org.springframework.core.io.Resource; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.FileCopyUtils; |
||||
|
||||
/** |
||||
* Internal helper used to load JSON from various sources. |
||||
* |
||||
* @author Phillip Webb |
||||
* @author Andy Wilkinson |
||||
* @author Stephane Nicoll |
||||
* @since 6.2 |
||||
*/ |
||||
class JsonLoader { |
||||
|
||||
@Nullable |
||||
private final Class<?> resourceLoadClass; |
||||
|
||||
private final Charset charset; |
||||
|
||||
JsonLoader(@Nullable Class<?> resourceLoadClass, @Nullable Charset charset) { |
||||
this.resourceLoadClass = resourceLoadClass; |
||||
this.charset = (charset != null ? charset : StandardCharsets.UTF_8); |
||||
} |
||||
|
||||
@Nullable |
||||
String getJson(@Nullable CharSequence source) { |
||||
if (source == null) { |
||||
return null; |
||||
} |
||||
if (source.toString().endsWith(".json")) { |
||||
return getJson(new ClassPathResource(source.toString(), this.resourceLoadClass)); |
||||
} |
||||
return source.toString(); |
||||
} |
||||
|
||||
String getJson(Resource source) { |
||||
try { |
||||
return getJson(source.getInputStream()); |
||||
} |
||||
catch (IOException ex) { |
||||
throw new IllegalStateException("Unable to load JSON from " + source, ex); |
||||
} |
||||
} |
||||
|
||||
private String getJson(InputStream source) throws IOException { |
||||
return FileCopyUtils.copyToString(new InputStreamReader(source, this.charset)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,165 @@
@@ -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 @@
@@ -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,9 @@
@@ -0,0 +1,9 @@
|
||||
/** |
||||
* Testing support for JSON. |
||||
*/ |
||||
@NonNullApi |
||||
@NonNullFields |
||||
package org.springframework.test.json; |
||||
|
||||
import org.springframework.lang.NonNullApi; |
||||
import org.springframework.lang.NonNullFields; |
||||
@ -0,0 +1,62 @@
@@ -0,0 +1,62 @@
|
||||
/* |
||||
* 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.util; |
||||
|
||||
import java.lang.reflect.Method; |
||||
|
||||
import org.assertj.core.api.AbstractObjectAssert; |
||||
import org.assertj.core.api.Assertions; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
|
||||
/** |
||||
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied |
||||
* to a {@link Method}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.2 |
||||
*/ |
||||
public class MethodAssert extends AbstractObjectAssert<MethodAssert, Method> { |
||||
|
||||
public MethodAssert(@Nullable Method actual) { |
||||
super(actual, MethodAssert.class); |
||||
as("Method %s", actual); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual method has the given {@linkplain Method#getName() |
||||
* name}. |
||||
* @param name the expected method name |
||||
*/ |
||||
public MethodAssert hasName(String name) { |
||||
isNotNull(); |
||||
Assertions.assertThat(this.actual.getName()).as("Method name").isEqualTo(name); |
||||
return this.myself; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual method is declared in the given {@code type}. |
||||
* @param type the expected declaring class
|
||||
*/ |
||||
public MethodAssert hasDeclaringClass(Class<?> type) { |
||||
isNotNull(); |
||||
Assertions.assertThat(this.actual.getDeclaringClass()) |
||||
.as("Method declaring class").isEqualTo(type); |
||||
return this.myself; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,123 @@
@@ -0,0 +1,123 @@
|
||||
/* |
||||
* 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.validation; |
||||
|
||||
import java.util.List; |
||||
|
||||
import org.assertj.core.api.AbstractAssert; |
||||
import org.assertj.core.api.AssertProvider; |
||||
import org.assertj.core.api.Assertions; |
||||
import org.assertj.core.api.ListAssert; |
||||
import org.assertj.core.error.BasicErrorMessageFactory; |
||||
import org.assertj.core.internal.Failures; |
||||
|
||||
import org.springframework.validation.BindingResult; |
||||
import org.springframework.validation.FieldError; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to |
||||
* {@link BindingResult}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.2 |
||||
* @param <SELF> the type of assertions |
||||
*/ |
||||
public abstract class AbstractBindingResultAssert<SELF extends AbstractBindingResultAssert<SELF>> extends AbstractAssert<SELF, BindingResult> { |
||||
|
||||
private final Failures failures = Failures.instance(); |
||||
|
||||
private final String name; |
||||
|
||||
protected AbstractBindingResultAssert(String name, BindingResult bindingResult, Class<?> selfType) { |
||||
super(bindingResult, selfType); |
||||
this.name = name; |
||||
as("Binding result for attribute '%s", this.name); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the total number of errors is equal to the given one. |
||||
* @param expected the expected number of errors |
||||
*/ |
||||
public SELF hasErrorsCount(int expected) { |
||||
assertThat(this.actual.getErrorCount()) |
||||
.as("check errors for attribute '%s'", this.name).isEqualTo(expected); |
||||
return this.myself; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual binding result contains fields in error with the |
||||
* given {@code fieldNames}. |
||||
* @param fieldNames the names of fields that should be in error |
||||
*/ |
||||
public SELF hasFieldErrors(String... fieldNames) { |
||||
assertThat(fieldErrorNames()).contains(fieldNames); |
||||
return this.myself; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual binding result contains <em>only</em> fields in |
||||
* error with the given {@code fieldNames}, and nothing else. |
||||
* @param fieldNames the exhaustive list of field name that should be in error |
||||
*/ |
||||
public SELF hasOnlyFieldErrors(String... fieldNames) { |
||||
assertThat(fieldErrorNames()).containsOnly(fieldNames); |
||||
return this.myself; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the field with the given {@code fieldName} has an error |
||||
* matching the given {@code errorCode}. |
||||
* @param fieldName the name of a field in error |
||||
* @param errorCode the error code for that field |
||||
*/ |
||||
public SELF hasFieldErrorCode(String fieldName, String errorCode) { |
||||
Assertions.assertThat(getFieldError(fieldName).getCode()) |
||||
.as("check error code for field '%s'", fieldName).isEqualTo(errorCode); |
||||
return this.myself; |
||||
} |
||||
|
||||
protected AssertionError unexpectedBindingResult(String reason, Object... arguments) { |
||||
return this.failures.failure(this.info, new UnexpectedBindingResult(reason, arguments)); |
||||
} |
||||
|
||||
private AssertProvider<ListAssert<String>> fieldErrorNames() { |
||||
return () -> { |
||||
List<String> actual = this.actual.getFieldErrors().stream().map(FieldError::getField).toList(); |
||||
return new ListAssert<>(actual).as("check field errors"); |
||||
}; |
||||
} |
||||
|
||||
private FieldError getFieldError(String fieldName) { |
||||
FieldError fieldError = this.actual.getFieldError(fieldName); |
||||
if (fieldError == null) { |
||||
throw unexpectedBindingResult("to have at least an error for field '%s'", fieldName); |
||||
} |
||||
return fieldError; |
||||
} |
||||
|
||||
|
||||
private final class UnexpectedBindingResult extends BasicErrorMessageFactory { |
||||
|
||||
private UnexpectedBindingResult(String reason, Object... arguments) { |
||||
super("%nExpecting binding result:%n %s%n%s", AbstractBindingResultAssert.this.actual, |
||||
reason.formatted(arguments)); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
/** |
||||
* Testing support for validation. |
||||
*/ |
||||
@NonNullApi |
||||
@NonNullFields |
||||
package org.springframework.test.validation; |
||||
|
||||
import org.springframework.lang.NonNullApi; |
||||
import org.springframework.lang.NonNullFields; |
||||
@ -0,0 +1,101 @@
@@ -0,0 +1,101 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web; |
||||
|
||||
import org.assertj.core.api.AbstractStringAssert; |
||||
import org.assertj.core.api.Assertions; |
||||
import org.assertj.core.error.BasicErrorMessageFactory; |
||||
import org.assertj.core.internal.Failures; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.AntPathMatcher; |
||||
import org.springframework.web.util.UriComponentsBuilder; |
||||
|
||||
/** |
||||
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied |
||||
* to a {@link String} representing a URI. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.2 |
||||
*/ |
||||
public class UriAssert extends AbstractStringAssert<UriAssert> { |
||||
|
||||
private static final AntPathMatcher pathMatcher = new AntPathMatcher(); |
||||
|
||||
private final String displayName; |
||||
|
||||
public UriAssert(@Nullable String actual, String displayName) { |
||||
super(actual, UriAssert.class); |
||||
this.displayName = displayName; |
||||
as(displayName); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual URI is equal to the URI built using the given |
||||
* {@code uriTemplate} and {@code uriVars}. |
||||
* Example: <pre><code class='java'> |
||||
* // Verify that uri is equal to "/orders/1/items/2"
|
||||
* assertThat(uri).isEqualToTemplate("/orders/{orderId}/items/{itemId}", 1, 2)); |
||||
* </code></pre> |
||||
* @param uriTemplate the expected URI string, with a number of URI |
||||
* template variables |
||||
* @param uriVars the values to replace the URI template variables |
||||
* @see UriComponentsBuilder#buildAndExpand(Object...) |
||||
*/ |
||||
public UriAssert isEqualToTemplate(String uriTemplate, Object... uriVars) { |
||||
String uri = buildUri(uriTemplate, uriVars); |
||||
return isEqualTo(uri); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual URI matches the given {@linkplain AntPathMatcher |
||||
* Ant-style} {@code uriPattern}. |
||||
* Example: <pre><code class='java'> |
||||
* // Verify that pattern matches "/orders/1/items/2"
|
||||
* assertThat(uri).matchPattern("/orders/*")); |
||||
* </code></pre> |
||||
* @param uriPattern the pattern that is expected to match |
||||
*/ |
||||
public UriAssert matchPattern(String uriPattern) { |
||||
Assertions.assertThat(pathMatcher.isPattern(uriPattern)) |
||||
.withFailMessage("'%s' is not an Ant-style path pattern", uriPattern).isTrue(); |
||||
Assertions.assertThat(pathMatcher.match(uriPattern, this.actual)) |
||||
.withFailMessage("%s '%s' does not match the expected URI pattern '%s'", |
||||
this.displayName, this.actual, uriPattern).isTrue(); |
||||
return this; |
||||
} |
||||
|
||||
private String buildUri(String uriTemplate, Object... uriVars) { |
||||
try { |
||||
return UriComponentsBuilder.fromUriString(uriTemplate) |
||||
.buildAndExpand(uriVars).encode().toUriString(); |
||||
} |
||||
catch (Exception ex) { |
||||
throw Failures.instance().failure(this.info, |
||||
new ShouldBeValidUriTemplate(uriTemplate, ex.getMessage())); |
||||
} |
||||
} |
||||
|
||||
|
||||
private static final class ShouldBeValidUriTemplate extends BasicErrorMessageFactory { |
||||
|
||||
private ShouldBeValidUriTemplate(String uriTemplate, String errorMessage) { |
||||
super("%nExpecting:%n %s%nTo be a valid URI template but got:%n %s%n", uriTemplate, errorMessage); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,122 @@
@@ -0,0 +1,122 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.util.Enumeration; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.Map; |
||||
import java.util.concurrent.Callable; |
||||
import java.util.function.Function; |
||||
import java.util.function.Supplier; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpSession; |
||||
import org.assertj.core.api.AbstractObjectAssert; |
||||
import org.assertj.core.api.Assertions; |
||||
import org.assertj.core.api.MapAssert; |
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.util.function.SingletonSupplier; |
||||
import org.springframework.web.context.request.async.DeferredResult; |
||||
|
||||
/** |
||||
* Base AssertJ {@link org.assertj.core.api.Assert assertions} that can be |
||||
* applied to a {@link HttpServletRequest}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.2 |
||||
* @param <SELF> the type of assertions |
||||
* @param <ACTUAL> the type of the object to assert |
||||
*/ |
||||
public abstract class AbstractHttpServletRequestAssert<SELF extends AbstractHttpServletRequestAssert<SELF, ACTUAL>, ACTUAL extends HttpServletRequest> |
||||
extends AbstractObjectAssert<SELF, ACTUAL> { |
||||
|
||||
private final Supplier<MapAssert<String, Object>> attributesAssertProvider; |
||||
|
||||
private final Supplier<MapAssert<String, Object>> sessionAttributesAssertProvider; |
||||
|
||||
protected AbstractHttpServletRequestAssert(ACTUAL actual, Class<?> selfType) { |
||||
super(actual, selfType); |
||||
this.attributesAssertProvider = SingletonSupplier.of(() -> createAttributesAssert(actual)); |
||||
this.sessionAttributesAssertProvider = SingletonSupplier.of(() -> createSessionAttributesAssert(actual)); |
||||
} |
||||
|
||||
private static MapAssert<String, Object> createAttributesAssert(HttpServletRequest request) { |
||||
Map<String, Object> map = toMap(request.getAttributeNames(), request::getAttribute); |
||||
return Assertions.assertThat(map).as("Request Attributes"); |
||||
} |
||||
|
||||
private static MapAssert<String, Object> createSessionAttributesAssert(HttpServletRequest request) { |
||||
HttpSession session = request.getSession(); |
||||
Assertions.assertThat(session).as("HTTP session").isNotNull(); |
||||
Map<String, Object> map = toMap(session.getAttributeNames(), session::getAttribute); |
||||
return Assertions.assertThat(map).as("Session Attributes"); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain MapAssert assertion} object that uses the request |
||||
* attributes as the object to test, with values mapped by attribute name. |
||||
* Examples: <pre><code class='java'> |
||||
* // Check for the presence of a request attribute named "attributeName":
|
||||
* assertThat(request).attributes().containsKey("attributeName"); |
||||
* </code></pre> |
||||
*/ |
||||
public MapAssert<String, Object> attributes() { |
||||
return this.attributesAssertProvider.get(); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain MapAssert assertion} object that uses the session |
||||
* attributes as the object to test, with values mapped by attribute name. |
||||
* Examples: <pre><code class='java'> |
||||
* // Check for the presence of a session attribute named "username":
|
||||
* assertThat(request).sessionAttributes().containsKey("username"); |
||||
* </code></pre> |
||||
*/ |
||||
public MapAssert<String, Object> sessionAttributes() { |
||||
return this.sessionAttributesAssertProvider.get(); |
||||
} |
||||
|
||||
/** |
||||
* Verify that whether asynchronous processing started, usually as a result |
||||
* of a controller method returning {@link Callable} or {@link DeferredResult}. |
||||
* <p>The test will await the completion of a {@code Callable} so that |
||||
* {@link MvcResultAssert#asyncResult()} can be used to assert the resulting |
||||
* value. |
||||
* <p>Neither a {@code Callable} nor a {@code DeferredResult} will complete |
||||
* processing all the way since a {@link MockHttpServletRequest} does not |
||||
* perform asynchronous dispatches. |
||||
* @param started whether asynchronous processing should have started |
||||
*/ |
||||
public SELF hasAsyncStarted(boolean started) { |
||||
Assertions.assertThat(this.actual.isAsyncStarted()) |
||||
.withFailMessage("Async expected to %s started", (started ? "have" : "not have")) |
||||
.isEqualTo(started); |
||||
return this.myself; |
||||
} |
||||
|
||||
|
||||
private static Map<String, Object> toMap(Enumeration<String> keys, Function<String, Object> valueProvider) { |
||||
Map<String, Object> map = new LinkedHashMap<>(); |
||||
while (keys.hasMoreElements()) { |
||||
String key = keys.nextElement(); |
||||
map.put(key, valueProvider.apply(key)); |
||||
} |
||||
return map; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,167 @@
@@ -0,0 +1,167 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.function.Supplier; |
||||
|
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
import org.assertj.core.api.AbstractIntegerAssert; |
||||
import org.assertj.core.api.AbstractMapAssert; |
||||
import org.assertj.core.api.AbstractObjectAssert; |
||||
import org.assertj.core.api.Assertions; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.HttpStatus.Series; |
||||
import org.springframework.test.http.HttpHeadersAssert; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
import org.springframework.util.function.SingletonSupplier; |
||||
|
||||
/** |
||||
* Base AssertJ {@link org.assertj.core.api.Assert assertions} that can be |
||||
* applied to any object that provides an {@link HttpServletResponse}. This |
||||
* allows to provide direct access to response assertions while providing |
||||
* access to a different top-level object. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.2 |
||||
* @param <R> the type of {@link HttpServletResponse} |
||||
* @param <SELF> the type of assertions |
||||
* @param <ACTUAL> the type of the object to assert |
||||
*/ |
||||
public abstract class AbstractHttpServletResponseAssert<R extends HttpServletResponse, SELF extends AbstractHttpServletResponseAssert<R, SELF, ACTUAL>, ACTUAL> |
||||
extends AbstractObjectAssert<SELF, ACTUAL> { |
||||
|
||||
private final Supplier<AbstractIntegerAssert<?>> statusAssert; |
||||
|
||||
private final Supplier<HttpHeadersAssert> headersAssertSupplier; |
||||
|
||||
|
||||
protected AbstractHttpServletResponseAssert(ACTUAL actual, Class<?> selfType) { |
||||
super(actual, selfType); |
||||
this.statusAssert = SingletonSupplier.of(() -> Assertions.assertThat(getResponse().getStatus()).as("HTTP status code")); |
||||
this.headersAssertSupplier = SingletonSupplier.of(() -> new HttpHeadersAssert(getHttpHeaders(getResponse()))); |
||||
} |
||||
|
||||
/** |
||||
* Provide the response to use if it is available. Throw an |
||||
* {@link AssertionError} if the request has failed to process and the |
||||
* response is not available. |
||||
* @return the response to use |
||||
*/ |
||||
protected abstract R getResponse(); |
||||
|
||||
/** |
||||
* Return a new {@linkplain HttpHeadersAssert assertion} object that uses |
||||
* the {@link HttpHeaders} as the object to test. The return assertion |
||||
* object provides all the regular {@linkplain AbstractMapAssert map |
||||
* assertions}, with headers mapped by header name. |
||||
* Examples: <pre><code class='java'> |
||||
* // Check for the presence of the Accept header:
|
||||
* assertThat(response).headers().containsHeader(HttpHeaders.ACCEPT); |
||||
* // Check for the absence of the Content-Length header:
|
||||
* assertThat(response).headers().doesNotContainsHeader(HttpHeaders.CONTENT_LENGTH); |
||||
* </code></pre> |
||||
*/ |
||||
public HttpHeadersAssert headers() { |
||||
return this.headersAssertSupplier.get(); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the HTTP status is equal to the specified status code. |
||||
* @param status the expected HTTP status code |
||||
*/ |
||||
public SELF hasStatus(int status) { |
||||
status().isEqualTo(status); |
||||
return this.myself; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the HTTP status is equal to the specified |
||||
* {@linkplain HttpStatus status}. |
||||
* @param status the expected HTTP status code |
||||
*/ |
||||
public SELF hasStatus(HttpStatus status) { |
||||
return hasStatus(status.value()); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the HTTP status is equal to {@link HttpStatus#OK}. |
||||
* @see #hasStatus(HttpStatus) |
||||
*/ |
||||
public SELF hasStatusOk() { |
||||
return hasStatus(HttpStatus.OK); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the HTTP status code is in the 1xx range. |
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc2616#section-10.1">RFC 2616</a> |
||||
*/ |
||||
public SELF hasStatus1xxInformational() { |
||||
return hasStatusSeries(Series.INFORMATIONAL); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the HTTP status code is in the 2xx range. |
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc2616#section-10.2">RFC 2616</a> |
||||
*/ |
||||
public SELF hasStatus2xxSuccessful() { |
||||
return hasStatusSeries(Series.SUCCESSFUL); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the HTTP status code is in the 3xx range. |
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc2616#section-10.3">RFC 2616</a> |
||||
*/ |
||||
public SELF hasStatus3xxRedirection() { |
||||
return hasStatusSeries(Series.REDIRECTION); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the HTTP status code is in the 4xx range. |
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc2616#section-10.4">RFC 2616</a> |
||||
*/ |
||||
public SELF hasStatus4xxClientError() { |
||||
return hasStatusSeries(Series.CLIENT_ERROR); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the HTTP status code is in the 5xx range. |
||||
* @see <a href="https://datatracker.ietf.org/doc/html/rfc2616#section-10.5">RFC 2616</a> |
||||
*/ |
||||
public SELF hasStatus5xxServerError() { |
||||
return hasStatusSeries(Series.SERVER_ERROR); |
||||
} |
||||
|
||||
private SELF hasStatusSeries(Series series) { |
||||
Assertions.assertThat(Series.resolve(getResponse().getStatus())).as("HTTP status series").isEqualTo(series); |
||||
return this.myself; |
||||
} |
||||
|
||||
private AbstractIntegerAssert<?> status() { |
||||
return this.statusAssert.get(); |
||||
} |
||||
|
||||
private static HttpHeaders getHttpHeaders(HttpServletResponse response) { |
||||
MultiValueMap<String, String> headers = new LinkedMultiValueMap<>(); |
||||
response.getHeaderNames().forEach(name -> headers.put(name, new ArrayList<>(response.getHeaders(name)))); |
||||
return new HttpHeaders(headers); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,38 @@
@@ -0,0 +1,38 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
|
||||
/** |
||||
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to |
||||
* {@link MockHttpServletRequest}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.2 |
||||
* @param <SELF> the type of assertions |
||||
*/ |
||||
public abstract class AbstractMockHttpServletRequestAssert<SELF extends AbstractMockHttpServletRequestAssert<SELF>> |
||||
extends AbstractHttpServletRequestAssert<SELF, MockHttpServletRequest> { |
||||
|
||||
protected AbstractMockHttpServletRequestAssert(MockHttpServletRequest request, Class<?> selfType) { |
||||
super(request, selfType); |
||||
} |
||||
|
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,109 @@
@@ -0,0 +1,109 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.nio.charset.Charset; |
||||
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.mock.web.MockHttpServletResponse; |
||||
import org.springframework.test.web.UriAssert; |
||||
|
||||
/** |
||||
* Extension of {@link AbstractHttpServletResponseAssert} for |
||||
* {@link MockHttpServletResponse}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.2 |
||||
* @param <SELF> the type of assertions |
||||
* @param <ACTUAL> the type of the object to assert |
||||
*/ |
||||
public abstract class AbstractMockHttpServletResponseAssert<SELF extends AbstractMockHttpServletResponseAssert<SELF, ACTUAL>, ACTUAL> |
||||
extends AbstractHttpServletResponseAssert<MockHttpServletResponse, SELF, ACTUAL> { |
||||
|
||||
@Nullable |
||||
private final GenericHttpMessageConverter<Object> jsonMessageConverter; |
||||
|
||||
protected AbstractMockHttpServletResponseAssert( |
||||
@Nullable GenericHttpMessageConverter<Object> jsonMessageConverter, ACTUAL actual, Class<?> selfType) { |
||||
|
||||
super(actual, selfType); |
||||
this.jsonMessageConverter = jsonMessageConverter; |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain ResponseBodyAssert assertion} object that uses |
||||
* the response body as the object to test. The return assertion object |
||||
* provides access to the raw byte array, a String value decoded using the |
||||
* response's character encoding, and dedicated json testing support. |
||||
* Examples: <pre><code class='java'> |
||||
* // Check that the response body is equal to "Hello World":
|
||||
* assertThat(response).body().isEqualTo("Hello World"); |
||||
* // Check that the response body is strictly equal to the content of "test.json":
|
||||
* assertThat(response).body().json().isStrictlyEqualToJson("test.json"); |
||||
* </code></pre> |
||||
*/ |
||||
public ResponseBodyAssert body() { |
||||
return new ResponseBodyAssert(getResponse().getContentAsByteArray(), |
||||
Charset.forName(getResponse().getCharacterEncoding()), this.jsonMessageConverter); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain UriAssert assertion} object that uses the |
||||
* forwarded URL as the object to test. If a simple equality check is |
||||
* required consider using {@link #hasForwardedUrl(String)} instead. |
||||
* Example: <pre><code class='java'> |
||||
* // Check that the forwarded URL starts with "/orders/":
|
||||
* assertThat(response).forwardedUrl().matchPattern("/orders/*); |
||||
* </code></pre> |
||||
*/ |
||||
public UriAssert forwardedUrl() { |
||||
return new UriAssert(getResponse().getForwardedUrl(), "Forwarded URL"); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain UriAssert assertion} object that uses the |
||||
* redirected URL as the object to test. If a simple equality check is |
||||
* required consider using {@link #hasRedirectedUrl(String)} instead. |
||||
* Example: <pre><code class='java'> |
||||
* // Check that the redirected URL starts with "/orders/":
|
||||
* assertThat(response).redirectedUrl().matchPattern("/orders/*); |
||||
* </code></pre> |
||||
*/ |
||||
public UriAssert redirectedUrl() { |
||||
return new UriAssert(getResponse().getRedirectedUrl(), "Redirected URL"); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the forwarded URL is equal to the given value. |
||||
* @param forwardedUrl the expected forwarded URL (can be null) |
||||
*/ |
||||
public SELF hasForwardedUrl(@Nullable String forwardedUrl) { |
||||
forwardedUrl().isEqualTo(forwardedUrl); |
||||
return this.myself; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the redirected URL is equal to the given value. |
||||
* @param redirectedUrl the expected redirected URL (can be null) |
||||
*/ |
||||
public SELF hasRedirectedUrl(@Nullable String redirectedUrl) { |
||||
redirectedUrl().isEqualTo(redirectedUrl); |
||||
return this.myself; |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,227 @@
@@ -0,0 +1,227 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.net.URI; |
||||
import java.util.Arrays; |
||||
import java.util.Collection; |
||||
import java.util.Map; |
||||
import java.util.function.Function; |
||||
import java.util.stream.StreamSupport; |
||||
|
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.converter.GenericHttpMessageConverter; |
||||
import org.springframework.http.converter.HttpMessageConverter; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.test.web.servlet.MockMvc; |
||||
import org.springframework.test.web.servlet.MvcResult; |
||||
import org.springframework.test.web.servlet.RequestBuilder; |
||||
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; |
||||
import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; |
||||
import org.springframework.test.web.servlet.setup.MockMvcBuilders; |
||||
import org.springframework.test.web.servlet.setup.StandaloneMockMvcBuilder; |
||||
import org.springframework.util.Assert; |
||||
import org.springframework.web.context.WebApplicationContext; |
||||
|
||||
/** |
||||
* {@link MockMvc} variant that tests Spring MVC exchanges and provide fluent |
||||
* assertions using {@link org.assertj.core.api.Assertions AssertJ}. |
||||
* |
||||
* <p>A main difference with {@link MockMvc} is that an unresolved exception |
||||
* is not thrown directly. Rather an {@link AssertableMvcResult} is available |
||||
* with an {@link AssertableMvcResult#getUnresolvedException() unresolved |
||||
* exception}. |
||||
* |
||||
* <p>{@link AssertableMockMvc} can be configured with a list of |
||||
* {@linkplain HttpMessageConverter HttpMessageConverters} to allow response |
||||
* body to be deserialized, rather than asserting on the raw values. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @author Brian Clozel |
||||
* @since 6.2 |
||||
*/ |
||||
public final class AssertableMockMvc { |
||||
|
||||
private static final MediaType JSON = MediaType.APPLICATION_JSON; |
||||
|
||||
private final MockMvc mockMvc; |
||||
|
||||
@Nullable |
||||
private final GenericHttpMessageConverter<Object> jsonMessageConverter; |
||||
|
||||
|
||||
private AssertableMockMvc(MockMvc mockMvc, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) { |
||||
Assert.notNull(mockMvc, "mockMVC should not be null"); |
||||
this.mockMvc = mockMvc; |
||||
this.jsonMessageConverter = jsonMessageConverter; |
||||
} |
||||
|
||||
/** |
||||
* Create a new {@link AssertableMockMvc} instance that delegates to the |
||||
* given {@link MockMvc}. |
||||
* @param mockMvc the MockMvc instance to delegate calls to |
||||
*/ |
||||
public static AssertableMockMvc create(MockMvc mockMvc) { |
||||
return new AssertableMockMvc(mockMvc, null); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@link AssertableMockMvc} instance using the given, fully |
||||
* initialized (i.e., <em>refreshed</em>) {@link WebApplicationContext}. The |
||||
* given {@code customizations} are applied to the {@link DefaultMockMvcBuilder} |
||||
* that ultimately creates the underlying {@link MockMvc} instance. |
||||
* <p>If no further customization of the underlying {@link MockMvc} instance |
||||
* is required, use {@link #from(WebApplicationContext)}. |
||||
* @param applicationContext the application context to detect the Spring |
||||
* MVC infrastructure and application controllers from |
||||
* @param customizations the function that creates a {@link MockMvc} |
||||
* instance based on a {@link DefaultMockMvcBuilder}. |
||||
* @see MockMvcBuilders#webAppContextSetup(WebApplicationContext) |
||||
*/ |
||||
public static AssertableMockMvc from(WebApplicationContext applicationContext, |
||||
Function<DefaultMockMvcBuilder, MockMvc> customizations) { |
||||
|
||||
DefaultMockMvcBuilder builder = MockMvcBuilders.webAppContextSetup(applicationContext); |
||||
MockMvc mockMvc = customizations.apply(builder); |
||||
return create(mockMvc); |
||||
} |
||||
|
||||
/** |
||||
* Shortcut to create a {@link AssertableMockMvc} instance using the given, |
||||
* fully initialized (i.e., <em>refreshed</em>) {@link WebApplicationContext}. |
||||
* <p>Consider using {@link #from(WebApplicationContext, Function)} if |
||||
* further customizations of the underlying {@link MockMvc} instance is |
||||
* required. |
||||
* @param applicationContext the application context to detect the Spring |
||||
* MVC infrastructure and application controllers from |
||||
* @see MockMvcBuilders#webAppContextSetup(WebApplicationContext) |
||||
*/ |
||||
public static AssertableMockMvc from(WebApplicationContext applicationContext) { |
||||
return from(applicationContext, DefaultMockMvcBuilder::build); |
||||
} |
||||
|
||||
/** |
||||
* Create a {@link AssertableMockMvc} instance by registering one or more |
||||
* {@code @Controller} instances and configuring Spring MVC infrastructure |
||||
* programmatically. |
||||
* <p>This allows full control over the instantiation and initialization of |
||||
* controllers and their dependencies, similar to plain unit tests while |
||||
* also making it possible to test one controller at a time. |
||||
* @param controllers one or more {@code @Controller} instances to test |
||||
* (specified {@code Class} will be turned into instance) |
||||
* @param customizations the function that creates a {@link MockMvc} |
||||
* instance based on a {@link StandaloneMockMvcBuilder}, typically to |
||||
* configure the Spring MVC infrastructure |
||||
* @see MockMvcBuilders#standaloneSetup(Object...) |
||||
*/ |
||||
public static AssertableMockMvc of(Collection<?> controllers, |
||||
Function<StandaloneMockMvcBuilder, MockMvc> customizations) { |
||||
|
||||
StandaloneMockMvcBuilder builder = MockMvcBuilders.standaloneSetup(controllers.toArray()); |
||||
return create(customizations.apply(builder)); |
||||
} |
||||
|
||||
/** |
||||
* Shortcut to create a {@link AssertableMockMvc} instance by registering |
||||
* one or more {@code @Controller} instances. |
||||
* <p>The minimum infrastructure required by the |
||||
* {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet} |
||||
* to serve requests with annotated controllers is created. Consider using |
||||
* {@link #of(Collection, Function)} if additional configuration of the MVC |
||||
* infrastructure is required. |
||||
* @param controllers one or more {@code @Controller} instances to test |
||||
* (specified {@code Class} will be turned into instance) |
||||
* @see MockMvcBuilders#standaloneSetup(Object...) |
||||
*/ |
||||
public static AssertableMockMvc of(Object... controllers) { |
||||
return of(Arrays.asList(controllers), StandaloneMockMvcBuilder::build); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@link AssertableMockMvc} instance using the specified |
||||
* {@link HttpMessageConverter}. If none are specified, only basic assertions |
||||
* on the response body can be performed. Consider registering a suitable |
||||
* JSON converter for asserting data structure. |
||||
* @param httpMessageConverters the message converters to use |
||||
* @return a new instance using the specified converters |
||||
*/ |
||||
public AssertableMockMvc withHttpMessageConverters(Iterable<HttpMessageConverter<?>> httpMessageConverters) { |
||||
return new AssertableMockMvc(this.mockMvc, findJsonMessageConverter(httpMessageConverters)); |
||||
} |
||||
|
||||
/** |
||||
* Perform a request and return a type that can be used with standard |
||||
* {@link org.assertj.core.api.Assertions AssertJ} assertions. |
||||
* <p>Use static methods of {@link MockMvcRequestBuilders} to prepare the |
||||
* request, wrapping the invocation in {@code assertThat}. The following |
||||
* asserts that a {@linkplain MockMvcRequestBuilders#get(URI) GET} request |
||||
* against "/greet" has an HTTP status code 200 (OK), and a simple body: |
||||
* <pre><code class='java'>assertThat(mvc.perform(get("/greet"))) |
||||
* .hasStatusOk() |
||||
* .body().asString().isEqualTo("Hello"); |
||||
* </code></pre> |
||||
* <p>Contrary to {@link MockMvc#perform(RequestBuilder)}, this does not |
||||
* throw an exception if the request fails with an unresolved exception. |
||||
* Rather, the result provides the exception, if any. Assuming that a |
||||
* {@linkplain MockMvcRequestBuilders#post(URI) POST} request against |
||||
* {@code /boom} throws an {@code IllegalStateException}, the following |
||||
* asserts that the invocation has indeed failed with the expected error |
||||
* message: |
||||
* <pre><code class='java'>assertThat(mvc.perform(post("/boom"))) |
||||
* .unresolvedException().isInstanceOf(IllegalStateException.class) |
||||
* .hasMessage("Expected"); |
||||
* </code></pre> |
||||
* <p> |
||||
* @param requestBuilder used to prepare the request to execute; |
||||
* see static factory methods in |
||||
* {@link org.springframework.test.web.servlet.request.MockMvcRequestBuilders} |
||||
* @return an {@link AssertableMvcResult} to be wrapped in {@code assertThat} |
||||
* @see MockMvc#perform(RequestBuilder) |
||||
*/ |
||||
public AssertableMvcResult perform(RequestBuilder requestBuilder) { |
||||
Object result = getMvcResultOrFailure(requestBuilder); |
||||
if (result instanceof MvcResult mvcResult) { |
||||
return new DefaultAssertableMvcResult(mvcResult, null, this.jsonMessageConverter); |
||||
} |
||||
else { |
||||
return new DefaultAssertableMvcResult(null, (Exception) result, this.jsonMessageConverter); |
||||
} |
||||
} |
||||
|
||||
private Object getMvcResultOrFailure(RequestBuilder requestBuilder) { |
||||
try { |
||||
return this.mockMvc.perform(requestBuilder).andReturn(); |
||||
} |
||||
catch (Exception ex) { |
||||
return ex; |
||||
} |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
@Nullable |
||||
private GenericHttpMessageConverter<Object> findJsonMessageConverter( |
||||
Iterable<HttpMessageConverter<?>> messageConverters) { |
||||
|
||||
return StreamSupport.stream(messageConverters.spliterator(), false) |
||||
.filter(GenericHttpMessageConverter.class::isInstance) |
||||
.map(GenericHttpMessageConverter.class::cast) |
||||
.filter(converter -> converter.canWrite(null, Map.class, JSON)) |
||||
.filter(converter -> converter.canRead(Map.class, JSON)) |
||||
.findFirst().orElse(null); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,50 @@
@@ -0,0 +1,50 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import org.assertj.core.api.AssertProvider; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.test.web.servlet.MvcResult; |
||||
|
||||
/** |
||||
* A {@link MvcResult} that additionally supports AssertJ style assertions. |
||||
* |
||||
* <p>Can be in two distinct states: |
||||
* <ol> |
||||
* <li>The request processed successfully, and {@link #getUnresolvedException()} |
||||
* is therefore {@code null}.</li> |
||||
* <li>The request failed unexpectedly with {@link #getUnresolvedException()} |
||||
* providing more information about the error. Any attempt to access a |
||||
* member of the result fails with an exception.</li> |
||||
* </ol> |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @author Brian Clozel |
||||
* @since 6.2 |
||||
* @see AssertableMockMvc |
||||
*/ |
||||
public interface AssertableMvcResult extends MvcResult, AssertProvider<MvcResultAssert> { |
||||
|
||||
/** |
||||
* Return the exception that was thrown unexpectedly while processing the |
||||
* request, if any. |
||||
*/ |
||||
@Nullable |
||||
Exception getUnresolvedException(); |
||||
|
||||
} |
||||
@ -0,0 +1,173 @@
@@ -0,0 +1,173 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.time.Duration; |
||||
import java.util.LinkedHashMap; |
||||
import java.util.Map; |
||||
import java.util.function.Consumer; |
||||
|
||||
import jakarta.servlet.http.Cookie; |
||||
import org.assertj.core.api.AbstractMapAssert; |
||||
import org.assertj.core.api.Assertions; |
||||
|
||||
/** |
||||
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to |
||||
* {@link Cookie cookies}. |
||||
* |
||||
* @author Brian Clozel |
||||
* @author Stephane Nicoll |
||||
* @since 6.2 |
||||
*/ |
||||
public class CookieMapAssert extends AbstractMapAssert<CookieMapAssert, Map<String, Cookie>, String, Cookie> { |
||||
|
||||
public CookieMapAssert(Cookie[] actual) { |
||||
super(mapCookies(actual), CookieMapAssert.class); |
||||
as("Cookies"); |
||||
} |
||||
|
||||
private static Map<String, Cookie> mapCookies(Cookie[] cookies) { |
||||
Map<String, Cookie> map = new LinkedHashMap<>(); |
||||
for (Cookie cookie : cookies) { |
||||
map.putIfAbsent(cookie.getName(), cookie); |
||||
} |
||||
return map; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual cookies contain a cookie with the given {@code name}. |
||||
* @param name the name of an expected cookie |
||||
* @see #containsKey |
||||
*/ |
||||
public CookieMapAssert containsCookie(String name) { |
||||
return containsKey(name); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual cookies contain the cookies with the given |
||||
* {@code names}. |
||||
* @param names the names of expected cookies |
||||
* @see #containsKeys |
||||
*/ |
||||
public CookieMapAssert containsCookies(String... names) { |
||||
return containsKeys(names); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual cookies do not contain a cookie with the |
||||
* given {@code name}. |
||||
* @param name the name of a cookie that should not be present |
||||
* @see #doesNotContainKey |
||||
*/ |
||||
public CookieMapAssert doesNotContainCookie(String name) { |
||||
return doesNotContainKey(name); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual cookies do not contain any of the cookies with |
||||
* the given {@code names}. |
||||
* @param names the names of cookies that should not be present |
||||
* @see #doesNotContainKeys |
||||
*/ |
||||
public CookieMapAssert doesNotContainCookies(String... names) { |
||||
return doesNotContainKeys(names); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual cookies contain a cookie with the given |
||||
* {@code name} that satisfy given {@code cookieRequirements}. |
||||
* the specified names. |
||||
* @param name the name of an expected cookie |
||||
* @param cookieRequirements the requirements for the cookie |
||||
*/ |
||||
public CookieMapAssert hasCookieSatisfying(String name, Consumer<Cookie> cookieRequirements) { |
||||
return hasEntrySatisfying(name, cookieRequirements); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual cookies contain a cookie with the given |
||||
* {@code name} whose {@linkplain Cookie#getValue() value} is equal to the |
||||
* given one. |
||||
* @param name the name of the cookie |
||||
* @param expected the expected value of the cookie |
||||
*/ |
||||
public CookieMapAssert hasValue(String name, String expected) { |
||||
return hasCookieSatisfying(name, cookie -> |
||||
Assertions.assertThat(cookie.getValue()).isEqualTo(expected)); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual cookies contain a cookie with the given |
||||
* {@code name} whose {@linkplain Cookie#getMaxAge() max age} is equal to |
||||
* the given one. |
||||
* @param name the name of the cookie |
||||
* @param expected the expected max age of the cookie |
||||
*/ |
||||
public CookieMapAssert hasMaxAge(String name, Duration expected) { |
||||
return hasCookieSatisfying(name, cookie -> |
||||
Assertions.assertThat(Duration.ofSeconds(cookie.getMaxAge())).isEqualTo(expected)); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual cookies contain a cookie with the given |
||||
* {@code name} whose {@linkplain Cookie#getPath() path} is equal to |
||||
* the given one. |
||||
* @param name the name of the cookie |
||||
* @param expected the expected path of the cookie |
||||
*/ |
||||
public CookieMapAssert hasPath(String name, String expected) { |
||||
return hasCookieSatisfying(name, cookie -> |
||||
Assertions.assertThat(cookie.getPath()).isEqualTo(expected)); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual cookies contain a cookie with the given |
||||
* {@code name} whose {@linkplain Cookie#getDomain() domain} is equal to |
||||
* the given one. |
||||
* @param name the name of the cookie |
||||
* @param expected the expected path of the cookie |
||||
*/ |
||||
public CookieMapAssert hasDomain(String name, String expected) { |
||||
return hasCookieSatisfying(name, cookie -> |
||||
Assertions.assertThat(cookie.getDomain()).isEqualTo(expected)); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual cookies contain a cookie with the given |
||||
* {@code name} whose {@linkplain Cookie#getSecure() secure flag} is equal |
||||
* to the given one. |
||||
* @param name the name of the cookie |
||||
* @param expected whether the cookie is secure |
||||
*/ |
||||
public CookieMapAssert isSecure(String name, boolean expected) { |
||||
return hasCookieSatisfying(name, cookie -> |
||||
Assertions.assertThat(cookie.getSecure()).isEqualTo(expected)); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual cookies contain a cookie with the given |
||||
* {@code name} whose {@linkplain Cookie#isHttpOnly() http only flag} is |
||||
* equal to the given one. |
||||
* @param name the name of the cookie |
||||
* @param expected whether the cookie is http only |
||||
*/ |
||||
public CookieMapAssert isHttpOnly(String name, boolean expected) { |
||||
return hasCookieSatisfying(name, cookie -> |
||||
Assertions.assertThat(cookie.isHttpOnly()).isEqualTo(expected)); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,123 @@
@@ -0,0 +1,123 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import org.springframework.http.converter.GenericHttpMessageConverter; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.mock.web.MockHttpServletResponse; |
||||
import org.springframework.test.web.servlet.MvcResult; |
||||
import org.springframework.web.servlet.FlashMap; |
||||
import org.springframework.web.servlet.HandlerInterceptor; |
||||
import org.springframework.web.servlet.ModelAndView; |
||||
|
||||
/** |
||||
* The default {@link AssertableMvcResult} implementation. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.2 |
||||
*/ |
||||
final class DefaultAssertableMvcResult implements AssertableMvcResult { |
||||
|
||||
@Nullable |
||||
private final MvcResult target; |
||||
|
||||
@Nullable |
||||
private final Exception unresolvedException; |
||||
|
||||
@Nullable |
||||
private final GenericHttpMessageConverter<Object> jsonMessageConverter; |
||||
|
||||
DefaultAssertableMvcResult(@Nullable MvcResult target, @Nullable Exception unresolvedException, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) { |
||||
this.target = target; |
||||
this.unresolvedException = unresolvedException; |
||||
this.jsonMessageConverter = jsonMessageConverter; |
||||
} |
||||
|
||||
/** |
||||
* Return the exception that was thrown unexpectedly while processing the |
||||
* request, if any. |
||||
*/ |
||||
@Nullable |
||||
public Exception getUnresolvedException() { |
||||
return this.unresolvedException; |
||||
} |
||||
|
||||
@Override |
||||
public MockHttpServletRequest getRequest() { |
||||
return getTarget().getRequest(); |
||||
} |
||||
|
||||
@Override |
||||
public MockHttpServletResponse getResponse() { |
||||
return getTarget().getResponse(); |
||||
} |
||||
|
||||
@Override |
||||
public Object getHandler() { |
||||
return getTarget().getHandler(); |
||||
} |
||||
|
||||
@Override |
||||
public HandlerInterceptor[] getInterceptors() { |
||||
return getTarget().getInterceptors(); |
||||
} |
||||
|
||||
@Override |
||||
public ModelAndView getModelAndView() { |
||||
return getTarget().getModelAndView(); |
||||
} |
||||
|
||||
@Override |
||||
public Exception getResolvedException() { |
||||
return getTarget().getResolvedException(); |
||||
} |
||||
|
||||
@Override |
||||
public FlashMap getFlashMap() { |
||||
return getTarget().getFlashMap(); |
||||
} |
||||
|
||||
@Override |
||||
public Object getAsyncResult() { |
||||
return getTarget().getAsyncResult(); |
||||
} |
||||
|
||||
@Override |
||||
public Object getAsyncResult(long timeToWait) { |
||||
return getTarget().getAsyncResult(timeToWait); |
||||
} |
||||
|
||||
|
||||
private MvcResult getTarget() { |
||||
if (this.target == null) { |
||||
throw new IllegalStateException( |
||||
"Request has failed with unresolved exception " + this.unresolvedException); |
||||
} |
||||
return this.target; |
||||
} |
||||
|
||||
/** |
||||
* Use AssertJ's {@link org.assertj.core.api.Assertions#assertThat assertThat} |
||||
* instead. |
||||
*/ |
||||
@Override |
||||
public MvcResultAssert assertThat() { |
||||
return new MvcResultAssert(this, this.jsonMessageConverter); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,120 @@
@@ -0,0 +1,120 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.lang.reflect.Method; |
||||
|
||||
import org.assertj.core.api.AbstractObjectAssert; |
||||
import org.assertj.core.api.Assertions; |
||||
|
||||
import org.springframework.cglib.core.internal.Function; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.test.util.MethodAssert; |
||||
import org.springframework.util.ClassUtils; |
||||
import org.springframework.web.method.HandlerMethod; |
||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder; |
||||
import org.springframework.web.servlet.mvc.method.annotation.MvcUriComponentsBuilder.MethodInvocationInfo; |
||||
|
||||
/** |
||||
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to |
||||
* a handler or handler method. |
||||
|
||||
* @author Stephane Nicoll |
||||
* @since 6.2 |
||||
*/ |
||||
public class HandlerResultAssert extends AbstractObjectAssert<HandlerResultAssert, Object> { |
||||
|
||||
public HandlerResultAssert(@Nullable Object actual) { |
||||
super(actual, HandlerResultAssert.class); |
||||
as("Handler result"); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain MethodAssert assertion} object that uses |
||||
* the {@link Method} that handles the request as the object to test. |
||||
* Verify first that the handler is a {@linkplain #isMethodHandler() method |
||||
* handler}. |
||||
* Example: <pre><code class='java'> |
||||
* // Check that a GET to "/greet" is invoked on a "handleGreet" method name
|
||||
* assertThat(mvc.perform(get("/greet")).handler().method().hasName("sayGreet"); |
||||
* </code></pre> |
||||
*/ |
||||
public MethodAssert method() { |
||||
return new MethodAssert(getHandlerMethod()); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the handler is managed by a method invocation, typically on |
||||
* a controller. |
||||
*/ |
||||
public HandlerResultAssert isMethodHandler() { |
||||
return isNotNull().isInstanceOf(HandlerMethod.class); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the handler is managed by the given {@code handlerMethod}. |
||||
* This creates a "mock" for the given {@code controllerType} and record the |
||||
* method invocation in the {@code handlerMethod}. The arguments used by the |
||||
* target method invocation can be {@code null} as the purpose of the mock |
||||
* is to identify the method that was invoked. |
||||
* Example: <pre><code class='java'> |
||||
* // If the method has a return type, you can return the result of the invocation
|
||||
* assertThat(mvc.perform(get("/greet")).handler().isInvokedOn( |
||||
* GreetController.class, controller -> controller.sayGreet()); |
||||
* // If the method has a void return type, the controller should be returned
|
||||
* assertThat(mvc.perform(post("/persons/")).handler().isInvokedOn( |
||||
* PersonController.class, controller -> controller.createPerson(null, null)); |
||||
* </code></pre> |
||||
* @param controllerType the controller to mock |
||||
* @param handlerMethod the method |
||||
*/ |
||||
public <T> HandlerResultAssert isInvokedOn(Class<T> controllerType, Function<T, Object> handlerMethod) { |
||||
MethodAssert actual = method(); |
||||
Object methodInvocationInfo = handlerMethod.apply(MvcUriComponentsBuilder.on(controllerType)); |
||||
Assertions.assertThat(methodInvocationInfo) |
||||
.as("Method invocation on controller '%s'", controllerType.getSimpleName()) |
||||
.isInstanceOfSatisfying(MethodInvocationInfo.class, mii -> |
||||
actual.isEqualTo(mii.getControllerMethod())); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the handler is of the given {@code type}. For a controller |
||||
* method, this is the type of the controller. |
||||
* Example: <pre><code class='java'> |
||||
* // Check that a GET to "/greet" is managed by GreetController
|
||||
* assertThat(mvc.perform(get("/greet")).handler().hasType(GreetController.class); |
||||
* </code></pre> |
||||
* @param type the expected type of the handler |
||||
*/ |
||||
public HandlerResultAssert hasType(Class<?> type) { |
||||
isNotNull(); |
||||
Class<?> actualType = this.actual.getClass(); |
||||
if (this.actual instanceof HandlerMethod handlerMethod) { |
||||
actualType = handlerMethod.getBeanType(); |
||||
} |
||||
Assertions.assertThat(ClassUtils.getUserClass(actualType)).as("Handler result type").isEqualTo(type); |
||||
return this; |
||||
} |
||||
|
||||
private Method getHandlerMethod() { |
||||
isMethodHandler(); // validate type
|
||||
return ((HandlerMethod) this.actual).getMethod(); |
||||
} |
||||
|
||||
|
||||
} |
||||
@ -0,0 +1,163 @@
@@ -0,0 +1,163 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.util.LinkedHashSet; |
||||
import java.util.Map; |
||||
import java.util.Set; |
||||
import java.util.function.Predicate; |
||||
|
||||
import org.assertj.core.api.AbstractMapAssert; |
||||
import org.assertj.core.error.BasicErrorMessageFactory; |
||||
import org.assertj.core.internal.Failures; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.test.validation.AbstractBindingResultAssert; |
||||
import org.springframework.validation.BindingResult; |
||||
import org.springframework.validation.BindingResultUtils; |
||||
import org.springframework.validation.Errors; |
||||
import org.springframework.web.servlet.ModelAndView; |
||||
|
||||
/** |
||||
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to |
||||
* a {@linkplain ModelAndView#getModel() model}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @since 6.2 |
||||
*/ |
||||
public class ModelAssert extends AbstractMapAssert<ModelAssert, Map<String, Object>, String, Object> { |
||||
|
||||
private final Failures failures = Failures.instance(); |
||||
|
||||
public ModelAssert(Map<String, Object> map) { |
||||
super(map, ModelAssert.class); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain AbstractBindingResultAssert assertion} object |
||||
* that uses the {@link BindingResult} with the given {@code name} as the |
||||
* object to test. |
||||
* Examples: <pre><code class='java'> |
||||
* // Check that the "person" attribute in the model has 2 errors:
|
||||
* assertThat(...).model().extractingBindingResult("person").hasErrorsCount(2); |
||||
* </code></pre> |
||||
*/ |
||||
public AbstractBindingResultAssert<?> extractingBindingResult(String name) { |
||||
BindingResult result = BindingResultUtils.getBindingResult(this.actual, name); |
||||
if (result == null) { |
||||
throw unexpectedModel("to have a binding result for attribute '%s'", name); |
||||
} |
||||
return new BindingResultAssert(name, result); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual model has at least one error. |
||||
*/ |
||||
public ModelAssert hasErrors() { |
||||
if (getAllErrors() == 0) { |
||||
throw unexpectedModel("to have at least one error"); |
||||
} |
||||
return this.myself; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual model does not have any errors. |
||||
*/ |
||||
public ModelAssert doesNotHaveErrors() { |
||||
int count = getAllErrors(); if (count > 0) { |
||||
throw unexpectedModel("to not have an error, but got %s", count); |
||||
} |
||||
return this.myself; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual model contain the attributes with the given |
||||
* {@code names}, and that these attributes have each at least one error. |
||||
* @param names the expected names of attributes with errors |
||||
*/ |
||||
public ModelAssert hasAttributeErrors(String... names) { |
||||
return assertAttributes(names, BindingResult::hasErrors, |
||||
"to have attribute errors for", "these attributes do not have any error"); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual model contain the attributes with the given |
||||
* {@code names}, and that these attributes do not have any error. |
||||
* @param names the expected names of attributes without errors |
||||
*/ |
||||
public ModelAssert doesNotHaveAttributeErrors(String... names) { |
||||
return assertAttributes(names, Predicate.not(BindingResult::hasErrors), |
||||
"to have attribute without errors for", "these attributes have at least an error"); |
||||
} |
||||
|
||||
private ModelAssert assertAttributes(String[] names, Predicate<BindingResult> condition, |
||||
String assertionMessage, String failAssertionMessage) { |
||||
|
||||
Set<String> missing = new LinkedHashSet<>(); |
||||
Set<String> failCondition = new LinkedHashSet<>(); |
||||
for (String name : names) { |
||||
BindingResult bindingResult = getBindingResult(name); |
||||
if (bindingResult == null) { |
||||
missing.add(name); |
||||
} |
||||
else if (!condition.test(bindingResult)) { |
||||
failCondition.add(name); |
||||
} |
||||
} |
||||
if (!missing.isEmpty() || !failCondition.isEmpty()) { |
||||
StringBuilder sb = new StringBuilder(); |
||||
sb.append("%n%s:%n %s%n".formatted(assertionMessage, String.join(", ", names))); |
||||
if (!missing.isEmpty()) { |
||||
sb.append("%nbut could not find these attributes:%n %s%n".formatted(String.join(", ", missing))); |
||||
} |
||||
if (!failCondition.isEmpty()) { |
||||
String prefix = missing.isEmpty() ? "but" : "and"; |
||||
sb.append("%n%s %s:%n %s%n".formatted(prefix, failAssertionMessage, String.join(", ", failCondition))); |
||||
} |
||||
throw unexpectedModel(sb.toString()); |
||||
} |
||||
return this.myself; |
||||
} |
||||
|
||||
private AssertionError unexpectedModel(String reason, Object... arguments) { |
||||
return this.failures.failure(this.info, new UnexpectedModel(reason, arguments)); |
||||
} |
||||
|
||||
private int getAllErrors() { |
||||
return this.actual.values().stream().filter(Errors.class::isInstance).map(Errors.class::cast) |
||||
.map(Errors::getErrorCount).reduce(0, Integer::sum); |
||||
} |
||||
|
||||
@Nullable |
||||
private BindingResult getBindingResult(String name) { |
||||
return BindingResultUtils.getBindingResult(this.actual, name); |
||||
} |
||||
|
||||
private final class UnexpectedModel extends BasicErrorMessageFactory { |
||||
|
||||
private UnexpectedModel(String reason, Object... arguments) { |
||||
super("%nExpecting model:%n %s%n%s", ModelAssert.this.actual, reason.formatted(arguments)); |
||||
} |
||||
} |
||||
|
||||
private static final class BindingResultAssert extends AbstractBindingResultAssert<BindingResultAssert> { |
||||
public BindingResultAssert(String name, BindingResult bindingResult) { |
||||
super(name, bindingResult, BindingResultAssert.class); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,258 @@
@@ -0,0 +1,258 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.io.BufferedReader; |
||||
import java.io.PrintWriter; |
||||
import java.io.StringReader; |
||||
import java.io.StringWriter; |
||||
|
||||
import jakarta.servlet.http.Cookie; |
||||
import org.assertj.core.api.AbstractStringAssert; |
||||
import org.assertj.core.api.AbstractThrowableAssert; |
||||
import org.assertj.core.api.Assertions; |
||||
import org.assertj.core.api.MapAssert; |
||||
import org.assertj.core.api.ObjectAssert; |
||||
import org.assertj.core.error.BasicErrorMessageFactory; |
||||
import org.assertj.core.internal.Failures; |
||||
|
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.converter.GenericHttpMessageConverter; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.mock.web.MockHttpServletResponse; |
||||
import org.springframework.test.http.MediaTypeAssert; |
||||
import org.springframework.test.web.servlet.MvcResult; |
||||
import org.springframework.test.web.servlet.ResultHandler; |
||||
import org.springframework.test.web.servlet.ResultMatcher; |
||||
import org.springframework.web.servlet.ModelAndView; |
||||
|
||||
/** |
||||
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied |
||||
* to {@link MvcResult}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @author Brian Clozel |
||||
* @since 6.2 |
||||
*/ |
||||
public class MvcResultAssert extends AbstractMockHttpServletResponseAssert<MvcResultAssert, AssertableMvcResult> { |
||||
|
||||
MvcResultAssert(AssertableMvcResult mvcResult, @Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) { |
||||
super(jsonMessageConverter, mvcResult, MvcResultAssert.class); |
||||
} |
||||
|
||||
@Override |
||||
protected MockHttpServletResponse getResponse() { |
||||
checkHasNotFailedUnexpectedly(); |
||||
return this.actual.getResponse(); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the request has failed with an unresolved exception, and |
||||
* return a new {@linkplain AbstractThrowableAssert assertion} object |
||||
* that uses the unresolved {@link Exception} as the object to test. |
||||
*/ |
||||
public AbstractThrowableAssert<?, ? extends Throwable> unresolvedException() { |
||||
hasUnresolvedException(); |
||||
return Assertions.assertThat(this.actual.getUnresolvedException()); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain AbstractMockHttpServletRequestAssert assertion} |
||||
* object that uses the {@link MockHttpServletRequest} as the object to test. |
||||
*/ |
||||
public AbstractMockHttpServletRequestAssert<?> request() { |
||||
checkHasNotFailedUnexpectedly(); |
||||
return new MockHttpRequestAssert(this.actual.getRequest()); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain CookieMapAssert assertion} object that uses the |
||||
* response's {@linkplain Cookie cookies} as the object to test. |
||||
*/ |
||||
public CookieMapAssert cookies() { |
||||
checkHasNotFailedUnexpectedly(); |
||||
return new CookieMapAssert(this.actual.getResponse().getCookies()); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain MediaTypeAssert assertion} object that uses the |
||||
* response's {@linkplain MediaType content type} as the object to test. |
||||
*/ |
||||
public MediaTypeAssert contentType() { |
||||
checkHasNotFailedUnexpectedly(); |
||||
return new MediaTypeAssert(this.actual.getResponse().getContentType()); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain HandlerResultAssert assertion} object that uses |
||||
* the handler as the object to test. For a method invocation on a |
||||
* controller, this is relative method handler |
||||
* Example: <pre><code class='java'> |
||||
* // Check that a GET to "/greet" is invoked on a "handleGreet" method name
|
||||
* assertThat(mvc.perform(get("/greet")).handler().method().hasName("sayGreet"); |
||||
* </code></pre> |
||||
*/ |
||||
public HandlerResultAssert handler() { |
||||
checkHasNotFailedUnexpectedly(); |
||||
return new HandlerResultAssert(this.actual.getHandler()); |
||||
} |
||||
|
||||
/** |
||||
* Verify that a {@link ModelAndView} is available and return a new |
||||
* {@linkplain ModelAssert assertion} object that uses the |
||||
* {@linkplain ModelAndView#getModel() model} as the object to test. |
||||
*/ |
||||
public ModelAssert model() { |
||||
checkHasNotFailedUnexpectedly(); |
||||
return new ModelAssert(getModelAndView().getModel()); |
||||
} |
||||
|
||||
/** |
||||
* Verify that a {@link ModelAndView} is available and return a new |
||||
* {@linkplain AbstractStringAssert assertion} object that uses the |
||||
* {@linkplain ModelAndView#getViewName()} view name} as the object to test. |
||||
* @see #hasViewName(String) |
||||
*/ |
||||
public AbstractStringAssert<?> viewName() { |
||||
checkHasNotFailedUnexpectedly(); |
||||
return Assertions.assertThat(getModelAndView().getViewName()).as("View name"); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain MapAssert assertion} object that uses the |
||||
* "output" flash attributes saved during request processing as the object |
||||
* to test. |
||||
*/ |
||||
public MapAssert<String, Object> flash() { |
||||
checkHasNotFailedUnexpectedly(); |
||||
return new MapAssert<>(this.actual.getFlashMap()); |
||||
} |
||||
|
||||
/** |
||||
* Verify that an {@linkplain AbstractHttpServletRequestAssert#hasAsyncStarted(boolean) |
||||
* asynchronous processing has started} and return a new |
||||
* {@linkplain ObjectAssert assertion} object that uses the asynchronous |
||||
* result as the object to test. |
||||
*/ |
||||
public ObjectAssert<Object> asyncResult() { |
||||
request().hasAsyncStarted(true); |
||||
return Assertions.assertThat(this.actual.getAsyncResult()).as("Async result"); |
||||
} |
||||
|
||||
/** |
||||
* Verify that the request has failed with an unresolved exception. |
||||
* @see #unresolvedException() |
||||
*/ |
||||
public MvcResultAssert hasUnresolvedException() { |
||||
Assertions.assertThat(this.actual.getUnresolvedException()) |
||||
.withFailMessage("Expecting request to have failed but it has succeeded").isNotNull(); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the request has not failed with an unresolved exception. |
||||
*/ |
||||
public MvcResultAssert doesNotHaveUnresolvedException() { |
||||
Assertions.assertThat(this.actual.getUnresolvedException()) |
||||
.withFailMessage("Expecting request to have succeeded but it has failed").isNull(); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Verify that the actual mvc result matches the given {@link ResultMatcher}. |
||||
* @param resultMatcher the result matcher to invoke |
||||
*/ |
||||
public MvcResultAssert matches(ResultMatcher resultMatcher) { |
||||
checkHasNotFailedUnexpectedly(); |
||||
return super.satisfies(resultMatcher::match); |
||||
} |
||||
|
||||
/** |
||||
* Apply the given {@link ResultHandler} to the actual mvc result. |
||||
* @param resultHandler the result matcher to invoke |
||||
*/ |
||||
public MvcResultAssert apply(ResultHandler resultHandler) { |
||||
checkHasNotFailedUnexpectedly(); |
||||
return satisfies(resultHandler::handle); |
||||
} |
||||
|
||||
/** |
||||
* Verify that a {@link ModelAndView} is available with a view equals to |
||||
* the given one. For more advanced assertions, consider using |
||||
* {@link #viewName()} |
||||
* @param viewName the expected view name |
||||
*/ |
||||
public MvcResultAssert hasViewName(String viewName) { |
||||
viewName().isEqualTo(viewName); |
||||
return this.myself; |
||||
} |
||||
|
||||
|
||||
private ModelAndView getModelAndView() { |
||||
ModelAndView modelAndView = this.actual.getModelAndView(); |
||||
Assertions.assertThat(modelAndView).as("ModelAndView").isNotNull(); |
||||
return modelAndView; |
||||
} |
||||
|
||||
protected void checkHasNotFailedUnexpectedly() { |
||||
Exception unresolvedException = this.actual.getUnresolvedException(); |
||||
if (unresolvedException != null) { |
||||
throw Failures.instance().failure(this.info, |
||||
new RequestFailedUnexpectedly(unresolvedException)); |
||||
} |
||||
} |
||||
|
||||
private static final class MockHttpRequestAssert extends AbstractMockHttpServletRequestAssert<MockHttpRequestAssert> { |
||||
|
||||
private MockHttpRequestAssert(MockHttpServletRequest request) { |
||||
super(request, MockHttpRequestAssert.class); |
||||
} |
||||
} |
||||
|
||||
private static final class RequestFailedUnexpectedly extends BasicErrorMessageFactory { |
||||
|
||||
private RequestFailedUnexpectedly(Exception ex) { |
||||
super("%nRequest has failed unexpectedly:%n%s", unquotedString(getIndentedStackTraceAsString(ex))); |
||||
} |
||||
|
||||
private static String getIndentedStackTraceAsString(Throwable ex) { |
||||
String stackTrace = getStackTraceAsString(ex); |
||||
return indent(stackTrace); |
||||
} |
||||
|
||||
private static String getStackTraceAsString(Throwable ex) { |
||||
StringWriter writer = new StringWriter(); |
||||
PrintWriter printer = new PrintWriter(writer); |
||||
ex.printStackTrace(printer); |
||||
return writer.toString(); |
||||
} |
||||
|
||||
private static String indent(String input) { |
||||
BufferedReader reader = new BufferedReader(new StringReader(input)); |
||||
StringWriter writer = new StringWriter(); |
||||
PrintWriter printer = new PrintWriter(writer); |
||||
reader.lines().forEach(line -> { |
||||
printer.print(" "); |
||||
printer.println(line); |
||||
}); |
||||
return writer.toString(); |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,125 @@
@@ -0,0 +1,125 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.nio.charset.Charset; |
||||
|
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
import org.assertj.core.api.AbstractByteArrayAssert; |
||||
import org.assertj.core.api.AbstractStringAssert; |
||||
|
||||
import org.springframework.core.io.ClassPathResource; |
||||
import org.springframework.http.converter.GenericHttpMessageConverter; |
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.test.json.JsonContentAssert; |
||||
import org.springframework.test.json.JsonPathAssert; |
||||
|
||||
/** |
||||
* AssertJ {@link org.assertj.core.api.Assert assertions} that can be applied to |
||||
* the response body. |
||||
* |
||||
* @author Stephane Nicoll |
||||
* @author Brian Clozel |
||||
* @since 6.2 |
||||
*/ |
||||
public class ResponseBodyAssert extends AbstractByteArrayAssert<ResponseBodyAssert> { |
||||
|
||||
private final Charset characterEncoding; |
||||
|
||||
@Nullable |
||||
private final GenericHttpMessageConverter<Object> jsonMessageConverter; |
||||
|
||||
ResponseBodyAssert(byte[] actual, Charset characterEncoding, |
||||
@Nullable GenericHttpMessageConverter<Object> jsonMessageConverter) { |
||||
|
||||
super(actual, ResponseBodyAssert.class); |
||||
this.characterEncoding = characterEncoding; |
||||
this.jsonMessageConverter = jsonMessageConverter; |
||||
as("Response body"); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain JsonPathAssert assertion} object that provides |
||||
* {@linkplain com.jayway.jsonpath.JsonPath JSON path} assertions on the |
||||
* response body. |
||||
*/ |
||||
public JsonPathAssert jsonPath() { |
||||
return new JsonPathAssert(getJson(), this.jsonMessageConverter); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain JsonContentAssert assertion} object that |
||||
* provides {@linkplain org.skyscreamer.jsonassert.JSONCompareMode JSON |
||||
* assert} comparison to expected json input that can be loaded from the |
||||
* classpath. Only absolute locations are supported, consider using |
||||
* {@link #json(Class)} to load json documents relative to a given class. |
||||
* Example: <pre><code class='java'> |
||||
* // Check that the response is strictly equal to the content of
|
||||
* // "/com/acme/web/person/person-created.json":
|
||||
* assertThat(...).body().json() |
||||
* .isStrictlyEqualToJson("/com/acme/web/person/person-created.json"); |
||||
* </code></pre> |
||||
*/ |
||||
public JsonContentAssert json() { |
||||
return json(null); |
||||
} |
||||
|
||||
/** |
||||
* Return a new {@linkplain JsonContentAssert assertion} object that |
||||
* provides {@linkplain org.skyscreamer.jsonassert.JSONCompareMode JSON |
||||
* assert} comparison to expected json input that can be loaded from the |
||||
* classpath. Documents can be absolute using a leading slash, or relative |
||||
* to the given {@code resourceLoadClass}. |
||||
* Example: <pre><code class='java'> |
||||
* // Check that the response is strictly equal to the content of
|
||||
* // the specified file:
|
||||
* assertThat(...).body().json(PersonController.class) |
||||
* .isStrictlyEqualToJson("person-created.json"); |
||||
* </code></pre> |
||||
* @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); |
||||
} |
||||
|
||||
/** |
||||
* Verifies that the response body is equal to the given {@link String}. |
||||
* <p>Convert the actual byte array to a String using the character encoding |
||||
* of the {@link HttpServletResponse}. |
||||
* @param expected the expected content of the response body |
||||
* @see #asString() |
||||
*/ |
||||
public ResponseBodyAssert isEqualTo(String expected) { |
||||
asString().isEqualTo(expected); |
||||
return this; |
||||
} |
||||
|
||||
/** |
||||
* Override that uses the character encoding of {@link HttpServletResponse} to |
||||
* convert the byte[] to a String, rather than the platform's default charset. |
||||
*/ |
||||
@Override |
||||
public AbstractStringAssert<?> asString() { |
||||
return asString(this.characterEncoding); |
||||
} |
||||
|
||||
private String getJson() { |
||||
return new String(this.actual, this.characterEncoding); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,9 @@
@@ -0,0 +1,9 @@
|
||||
/** |
||||
* AssertJ support for MockMvc. |
||||
*/ |
||||
@NonNullApi |
||||
@NonNullFields |
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import org.springframework.lang.NonNullApi; |
||||
import org.springframework.lang.NonNullFields; |
||||
@ -0,0 +1,190 @@
@@ -0,0 +1,190 @@
|
||||
/* |
||||
* 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.http; |
||||
|
||||
import java.time.Instant; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
|
||||
import org.assertj.core.api.Assertions; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.util.LinkedMultiValueMap; |
||||
import org.springframework.util.MultiValueMap; |
||||
|
||||
/** |
||||
* Tests for {@link HttpHeadersAssert}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class HttpHeadersAssertTests { |
||||
|
||||
@Test |
||||
void containsHeader() { |
||||
assertThat(Map.of("first", "1")).containsHeader("first"); |
||||
} |
||||
|
||||
@Test |
||||
void containsHeaderWithNameNotPresent() { |
||||
Map<String, String> map = Map.of("first", "1"); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(map).containsHeader("wrong-name")) |
||||
.withMessageContainingAll("HTTP headers", "first", "wrong-name"); |
||||
} |
||||
|
||||
@Test |
||||
void containsHeaders() { |
||||
assertThat(Map.of("first", "1", "second", "2", "third", "3")) |
||||
.containsHeaders("first", "third"); |
||||
} |
||||
|
||||
@Test |
||||
void containsHeadersWithSeveralNamesNotPresent() { |
||||
Map<String, String> map = Map.of("first", "1", "second", "2", "third", "3"); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(map).containsHeaders("first", "wrong-name", "another-wrong-name", "third")) |
||||
.withMessageContainingAll("HTTP headers", "first", "wrong-name", "another-wrong-name"); |
||||
} |
||||
|
||||
@Test |
||||
void doesNotContainsHeader() { |
||||
assertThat(Map.of("first", "1")).doesNotContainsHeader("second"); |
||||
} |
||||
|
||||
@Test |
||||
void doesNotContainsHeaderWithNamePresent() { |
||||
Map<String, String> map = Map.of("first", "1"); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(map).doesNotContainKey("first")) |
||||
.withMessageContainingAll("HTTP headers", "first"); |
||||
} |
||||
|
||||
@Test |
||||
void doesNotContainsHeaders() { |
||||
assertThat(Map.of("first", "1", "third", "3")) |
||||
.doesNotContainsHeaders("second", "fourth"); |
||||
} |
||||
|
||||
@Test |
||||
void doesNotContainsHeadersWithSeveralNamesPresent() { |
||||
Map<String, String> map = Map.of("first", "1", "second", "2", "third", "3"); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(map).doesNotContainsHeaders("first", "another-wrong-name", "second")) |
||||
.withMessageContainingAll("HTTP headers", "first", "second"); |
||||
} |
||||
|
||||
|
||||
@Test |
||||
void hasValueWithStringMatch() { |
||||
HttpHeaders headers = new HttpHeaders(); |
||||
headers.addAll("header", List.of("a", "b", "c")); |
||||
assertThat(headers).hasValue("header", "a"); |
||||
} |
||||
|
||||
@Test |
||||
void hasValueWithStringMatchOnSecondaryValue() { |
||||
HttpHeaders headers = new HttpHeaders(); |
||||
headers.addAll("header", List.of("first", "second", "third")); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(headers).hasValue("header", "second")) |
||||
.withMessageContainingAll("check primary value for HTTP header 'header'", "first", "second"); |
||||
} |
||||
|
||||
@Test |
||||
void hasValueWithNoStringMatch() { |
||||
HttpHeaders headers = new HttpHeaders(); |
||||
headers.addAll("header", List.of("first", "second", "third")); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(headers).hasValue("wrong-name", "second")) |
||||
.withMessageContainingAll("HTTP headers", "header", "wrong-name"); |
||||
} |
||||
|
||||
@Test |
||||
void hasValueWithNonPresentHeader() { |
||||
HttpHeaders map = new HttpHeaders(); |
||||
map.add("test-header", "a"); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(map).hasValue("wrong-name", "a")) |
||||
.withMessageContainingAll("HTTP headers", "test-header", "wrong-name"); |
||||
} |
||||
|
||||
@Test |
||||
void hasValueWithLongMatch() { |
||||
HttpHeaders headers = new HttpHeaders(); |
||||
headers.addAll("header", List.of("123", "456", "789")); |
||||
assertThat(headers).hasValue("header", 123); |
||||
} |
||||
|
||||
@Test |
||||
void hasValueWithLongMatchOnSecondaryValue() { |
||||
HttpHeaders map = new HttpHeaders(); |
||||
map.addAll("header", List.of("123", "456", "789")); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(map).hasValue("header", 456)) |
||||
.withMessageContainingAll("check primary long value for HTTP header 'header'", "123", "456"); |
||||
} |
||||
|
||||
@Test |
||||
void hasValueWithNoLongMatch() { |
||||
HttpHeaders map = new HttpHeaders(); |
||||
map.addAll("header", List.of("123", "456", "789")); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(map).hasValue("wrong-name", 456)) |
||||
.withMessageContainingAll("HTTP headers", "header", "wrong-name"); |
||||
} |
||||
|
||||
@Test |
||||
void hasValueWithInstantMatch() { |
||||
Instant instant = Instant.now(); |
||||
HttpHeaders headers = new HttpHeaders(); |
||||
headers.setInstant("header", instant); |
||||
assertThat(headers).hasValue("header", instant); |
||||
} |
||||
|
||||
@Test |
||||
void hasValueWithNoInstantMatch() { |
||||
Instant instant = Instant.now(); |
||||
HttpHeaders map = new HttpHeaders(); |
||||
map.setInstant("header", instant); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(map).hasValue("wrong-name", instant.minusSeconds(30))) |
||||
.withMessageContainingAll("HTTP headers", "header", "wrong-name"); |
||||
} |
||||
|
||||
@Test |
||||
void hasValueWithNoInstantMatchOneSecOfDifference() { |
||||
Instant instant = Instant.now(); |
||||
HttpHeaders map = new HttpHeaders(); |
||||
map.setInstant("header", instant); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(map).hasValue("wrong-name", instant.minusSeconds(1))) |
||||
.withMessageContainingAll("HTTP headers", "header", "wrong-name"); |
||||
} |
||||
|
||||
|
||||
private static HttpHeadersAssert assertThat(Map<String, String> values) { |
||||
MultiValueMap<String, String> map = new LinkedMultiValueMap<>(); |
||||
values.forEach(map::add); |
||||
return assertThat(new HttpHeaders(map)); |
||||
} |
||||
|
||||
private static HttpHeadersAssert assertThat(HttpHeaders values) { |
||||
return new HttpHeadersAssert(values); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,157 @@
@@ -0,0 +1,157 @@
|
||||
/* |
||||
* 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.http; |
||||
|
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.lang.Nullable; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
|
||||
/** |
||||
* Tests for {@link MediaTypeAssert}. |
||||
* |
||||
* @author Brian Clozel |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class MediaTypeAssertTests { |
||||
|
||||
@Test |
||||
void actualCanBeNull() { |
||||
new MediaTypeAssert((MediaType) null).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void actualStringCanBeNull() { |
||||
new MediaTypeAssert((String) null).isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualWhenSameShouldPass() { |
||||
assertThat(mediaType("application/json")).isEqualTo("application/json"); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualWhenDifferentShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(mediaType("application/json")).isEqualTo("text/html")) |
||||
.withMessageContaining("Media type"); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualWhenActualIsNullShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(null).isEqualTo(MediaType.APPLICATION_JSON)) |
||||
.withMessageContaining("Media type"); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualWhenSameTypeShouldPass() { |
||||
assertThat(mediaType("application/json")).isEqualTo(MediaType.APPLICATION_JSON); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualWhenDifferentTypeShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(mediaType("application/json")).isEqualTo(MediaType.TEXT_HTML)) |
||||
.withMessageContaining("Media type"); |
||||
} |
||||
|
||||
@Test |
||||
void isCompatibleWhenSameShouldPass() { |
||||
assertThat(mediaType("application/json")).isCompatibleWith("application/json"); |
||||
} |
||||
|
||||
@Test |
||||
void isCompatibleWhenCompatibleShouldPass() { |
||||
assertThat(mediaType("application/json")).isCompatibleWith("application/*"); |
||||
} |
||||
|
||||
@Test |
||||
void isCompatibleWhenDifferentShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(mediaType("application/json")).isCompatibleWith("text/html")) |
||||
.withMessageContaining("check media type 'application/json' is compatible with 'text/html'"); |
||||
} |
||||
|
||||
@Test |
||||
void isCompatibleWithStringAndNullActual() { |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(null).isCompatibleWith("text/html")) |
||||
.withMessageContaining("Expecting null to be compatible with 'text/html'"); |
||||
} |
||||
|
||||
@Test |
||||
void isCompatibleWithStringAndNullExpected() { |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(mediaType("application/json")).isCompatibleWith((String) null)) |
||||
.withMessageContainingAll("Expecting:", "null", "To be a valid media type but got:", |
||||
"'mimeType' must not be empty"); |
||||
} |
||||
|
||||
@Test |
||||
void isCompatibleWithStringAndEmptyExpected() { |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(mediaType("application/json")).isCompatibleWith("")) |
||||
.withMessageContainingAll("Expecting:", "", "To be a valid media type but got:", |
||||
"'mimeType' must not be empty"); |
||||
} |
||||
|
||||
@Test |
||||
void isCompatibleWithMediaTypeAndNullActual() { |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(null).isCompatibleWith(MediaType.TEXT_HTML)) |
||||
.withMessageContaining("Expecting null to be compatible with 'text/html'"); |
||||
} |
||||
|
||||
@Test |
||||
void isCompatibleWithMediaTypeAndNullExpected() { |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(mediaType("application/json")).isCompatibleWith((MediaType) null)) |
||||
.withMessageContaining("Expecting 'application/json' to be compatible with null"); |
||||
} |
||||
|
||||
@Test |
||||
void isCompatibleWhenSameTypeShouldPass() { |
||||
assertThat(mediaType("application/json")).isCompatibleWith(MediaType.APPLICATION_JSON); |
||||
} |
||||
|
||||
@Test |
||||
void isCompatibleWhenCompatibleTypeShouldPass() { |
||||
assertThat(mediaType("application/json")).isCompatibleWith(MediaType.parseMediaType("application/*")); |
||||
} |
||||
|
||||
@Test |
||||
void isCompatibleWhenDifferentTypeShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(mediaType("application/json")).isCompatibleWith(MediaType.TEXT_HTML)) |
||||
.withMessageContaining("check media type 'application/json' is compatible with 'text/html'"); |
||||
} |
||||
|
||||
|
||||
@Nullable |
||||
private static MediaType mediaType(@Nullable String mediaType) { |
||||
return (mediaType != null ? MediaType.parseMediaType(mediaType) : null); |
||||
} |
||||
|
||||
private static MediaTypeAssert assertThat(@Nullable MediaType mediaType) { |
||||
return new MediaTypeAssert(mediaType); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,479 @@
@@ -0,0 +1,479 @@
|
||||
/* |
||||
* 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); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,60 @@
@@ -0,0 +1,60 @@
|
||||
/* |
||||
* 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 org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; |
||||
|
||||
/** |
||||
* Tests for {@link JsonContent}. |
||||
* |
||||
* @author Phillip Webb |
||||
*/ |
||||
class JsonContentTests { |
||||
|
||||
private static final String JSON = "{\"name\":\"spring\", \"age\":100}"; |
||||
|
||||
@Test |
||||
void createWhenJsonIsNullShouldThrowException() { |
||||
assertThatIllegalArgumentException() |
||||
.isThrownBy( |
||||
() -> new JsonContent(null, null)) |
||||
.withMessageContaining("JSON must not be null"); |
||||
} |
||||
|
||||
@Test |
||||
@SuppressWarnings("deprecation") |
||||
void assertThatShouldReturnJsonContentAssert() { |
||||
JsonContent content = new JsonContent(JSON, getClass()); |
||||
assertThat(content.assertThat()).isInstanceOf(JsonContentAssert.class); |
||||
} |
||||
|
||||
@Test |
||||
void getJsonShouldReturnJson() { |
||||
JsonContent content = new JsonContent(JSON, getClass()); |
||||
assertThat(content.getJson()).isEqualTo(JSON); |
||||
} |
||||
|
||||
@Test |
||||
void toStringShouldReturnString() { |
||||
JsonContent content = new JsonContent(JSON, getClass()); |
||||
assertThat(content.toString()).isEqualTo("JsonContent " + JSON); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,322 @@
@@ -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 @@
@@ -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); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,92 @@
@@ -0,0 +1,92 @@
|
||||
/* |
||||
* 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.util; |
||||
|
||||
import java.lang.reflect.Method; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.lang.Nullable; |
||||
import org.springframework.util.ReflectionUtils; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
|
||||
/** |
||||
* Tests for {@link MethodAssert}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class MethodAssertTests { |
||||
|
||||
@Test |
||||
void isEqualTo() { |
||||
Method method = ReflectionUtils.findMethod(TestData.class, "counter"); |
||||
assertThat(method).isEqualTo(method); |
||||
} |
||||
|
||||
@Test |
||||
void hasName() { |
||||
assertThat(ReflectionUtils.findMethod(TestData.class, "counter")).hasName("counter"); |
||||
} |
||||
|
||||
@Test |
||||
void hasNameWithWrongName() { |
||||
Method method = ReflectionUtils.findMethod(TestData.class, "counter"); |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(method).hasName("invalid")) |
||||
.withMessageContainingAll("Method name", "counter", "invalid"); |
||||
} |
||||
|
||||
@Test |
||||
void hasNameWithNullMethod() { |
||||
Method method = ReflectionUtils.findMethod(TestData.class, "notAMethod"); |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(method).hasName("name")) |
||||
.withMessageContaining("Expecting actual not to be null"); |
||||
} |
||||
|
||||
@Test |
||||
void hasDeclaringClass() { |
||||
assertThat(ReflectionUtils.findMethod(TestData.class, "counter")).hasDeclaringClass(TestData.class); |
||||
} |
||||
|
||||
@Test |
||||
void haDeclaringClassWithWrongClass() { |
||||
Method method = ReflectionUtils.findMethod(TestData.class, "counter"); |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(method).hasDeclaringClass(Method.class)) |
||||
.withMessageContainingAll("Method declaring class", |
||||
TestData.class.getCanonicalName(), Method.class.getCanonicalName()); |
||||
} |
||||
|
||||
@Test |
||||
void hasDeclaringClassWithNullMethod() { |
||||
Method method = ReflectionUtils.findMethod(TestData.class, "notAMethod"); |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(method).hasDeclaringClass(TestData.class)) |
||||
.withMessageContaining("Expecting actual not to be null"); |
||||
} |
||||
|
||||
|
||||
private MethodAssert assertThat(@Nullable Method method) { |
||||
return new MethodAssert(method); |
||||
} |
||||
|
||||
|
||||
record TestData(String name, int counter) {} |
||||
|
||||
} |
||||
@ -0,0 +1,136 @@
@@ -0,0 +1,136 @@
|
||||
/* |
||||
* 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.validation; |
||||
|
||||
import java.util.Map; |
||||
|
||||
import org.assertj.core.api.AssertProvider; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.beans.MutablePropertyValues; |
||||
import org.springframework.beans.testfixture.beans.TestBean; |
||||
import org.springframework.validation.BindException; |
||||
import org.springframework.validation.BindingResult; |
||||
import org.springframework.validation.DataBinder; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
|
||||
/** |
||||
* Tests for {@link AbstractBindingResultAssert}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class AbstractBindingResultAssertTests { |
||||
|
||||
@Test |
||||
void hasErrorsCountWithNoError() { |
||||
assertThat(bindingResult(new TestBean(), Map.of("name", "John", "age", "42"))).hasErrorsCount(0); |
||||
} |
||||
|
||||
@Test |
||||
void hasErrorsCountWithInvalidCount() { |
||||
AssertProvider<BindingResultAssert> actual = bindingResult(new TestBean(), |
||||
Map.of("name", "John", "age", "4x", "touchy", "invalid.value")); |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||
() -> assertThat(actual).hasErrorsCount(1)) |
||||
.withMessageContainingAll("check errors for attribute 'test'", "1", "2"); |
||||
} |
||||
|
||||
@Test |
||||
void hasFieldErrorsWithMatchingSubset() { |
||||
assertThat(bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y"))) |
||||
.hasFieldErrors("touchy"); |
||||
} |
||||
|
||||
@Test |
||||
void hasFieldErrorsWithAllMatching() { |
||||
assertThat(bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y"))) |
||||
.hasFieldErrors("touchy", "age"); |
||||
} |
||||
|
||||
@Test |
||||
void hasFieldErrorsWithNotAllMatching() { |
||||
AssertProvider<BindingResultAssert> actual = bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y")); |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||
() -> assertThat(actual).hasFieldErrors("age", "name")) |
||||
.withMessageContainingAll("check field errors", "age", "touchy", "name"); |
||||
} |
||||
|
||||
@Test |
||||
void hasOnlyFieldErrorsWithAllMatching() { |
||||
assertThat(bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y"))) |
||||
.hasOnlyFieldErrors("touchy", "age"); |
||||
} |
||||
|
||||
@Test |
||||
void hasOnlyFieldErrorsWithMatchingSubset() { |
||||
AssertProvider<BindingResultAssert> actual = bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y")); |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||
() -> assertThat(actual).hasOnlyFieldErrors("age")) |
||||
.withMessageContainingAll("check field errors", "age", "touchy"); |
||||
} |
||||
|
||||
@Test |
||||
void hasFieldErrorCodeWithMatchingCode() { |
||||
assertThat(bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y"))) |
||||
.hasFieldErrorCode("age", "typeMismatch"); |
||||
} |
||||
|
||||
@Test |
||||
void hasFieldErrorCodeWitNonMatchingCode() { |
||||
AssertProvider<BindingResultAssert> actual = bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y")); |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||
() -> assertThat(actual).hasFieldErrorCode("age", "castFailure")) |
||||
.withMessageContainingAll("check error code for field 'age'", "castFailure", "typeMismatch"); |
||||
} |
||||
|
||||
@Test |
||||
void hasFieldErrorCodeWitNonMatchingField() { |
||||
AssertProvider<BindingResultAssert> actual = bindingResult(new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "x.y")); |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||
() -> assertThat(actual).hasFieldErrorCode("unknown", "whatever")) |
||||
.withMessageContainingAll("Expecting binding result", "touchy", "age", |
||||
"to have at least an error for field 'unknown'"); |
||||
} |
||||
|
||||
|
||||
private AssertProvider<BindingResultAssert> bindingResult(Object instance, Map<String, Object> propertyValues) { |
||||
return () -> new BindingResultAssert("test", createBindingResult(instance, propertyValues)); |
||||
} |
||||
|
||||
private static BindingResult createBindingResult(Object instance, Map<String, Object> propertyValues) { |
||||
DataBinder binder = new DataBinder(instance, "test"); |
||||
MutablePropertyValues pvs = new MutablePropertyValues(propertyValues); |
||||
binder.bind(pvs); |
||||
try { |
||||
binder.close(); |
||||
return binder.getBindingResult(); |
||||
} |
||||
catch (BindException ex) { |
||||
return ex.getBindingResult(); |
||||
} |
||||
} |
||||
|
||||
|
||||
private static final class BindingResultAssert extends AbstractBindingResultAssert<BindingResultAssert> { |
||||
public BindingResultAssert(String name, BindingResult bindingResult) { |
||||
super(name, bindingResult, BindingResultAssert.class); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
|
||||
/** |
||||
* Tests for {@link UriAssert}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class UriAssertTests { |
||||
|
||||
@Test |
||||
void isEqualToTemplate() { |
||||
assertThat("/orders/1/items/2").isEqualToTemplate("/orders/{orderId}/items/{itemId}", 1, 2); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualToTemplateWithWrongValue() { |
||||
String expected = "/orders/1/items/3"; |
||||
String actual = "/orders/1/items/2"; |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(expected).isEqualToTemplate("/orders/{orderId}/items/{itemId}", 1, 2)) |
||||
.withMessageContainingAll("Test URI", expected, actual); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualToTemplateMissingArg() { |
||||
String template = "/orders/{orderId}/items/{itemId}"; |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat("/orders/1/items/2").isEqualToTemplate(template, 1)) |
||||
.withMessageContainingAll("Expecting:", template, |
||||
"Not enough variable values available to expand 'itemId'"); |
||||
} |
||||
|
||||
@Test |
||||
void matchPattern() { |
||||
assertThat("/orders/1").matchPattern("/orders/*"); |
||||
} |
||||
|
||||
@Test |
||||
void matchPatternWithNonValidPattern() { |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat("/orders/1").matchPattern("/orders/")) |
||||
.withMessage("'/orders/' is not an Ant-style path pattern"); |
||||
} |
||||
|
||||
@Test |
||||
void matchPatternWithWrongValue() { |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat("/orders/1").matchPattern("/resources/*")) |
||||
.withMessageContainingAll("Test URI", "/resources/*", "/orders/1"); |
||||
} |
||||
|
||||
|
||||
UriAssert assertThat(String uri) { |
||||
return new UriAssert(uri, "Test URI"); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,143 @@
@@ -0,0 +1,143 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.util.LinkedHashMap; |
||||
import java.util.Map; |
||||
|
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpSession; |
||||
import org.assertj.core.api.Assertions; |
||||
import org.junit.jupiter.api.Nested; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
|
||||
import static java.util.Map.entry; |
||||
|
||||
/** |
||||
* Tests for {@link AbstractHttpServletRequestAssert}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
public class AbstractHttpServletRequestAssertTests { |
||||
|
||||
|
||||
@Nested |
||||
class AttributesTests { |
||||
|
||||
@Test |
||||
void attributesAreCopied() { |
||||
Map<String, Object> map = new LinkedHashMap<>(); |
||||
map.put("one", 1); |
||||
map.put("two", 2); |
||||
assertThat(createRequest(map)).attributes() |
||||
.containsExactly(entry("one", 1), entry("two", 2)); |
||||
} |
||||
|
||||
@Test |
||||
void attributesWithWrongKey() { |
||||
HttpServletRequest request = createRequest(Map.of("one", 1)); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(request).attributes().containsKey("two")) |
||||
.withMessageContainingAll("Request Attributes", "two", "one"); |
||||
} |
||||
|
||||
private HttpServletRequest createRequest(Map<String, Object> attributes) { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
attributes.forEach(request::setAttribute); |
||||
return request; |
||||
} |
||||
|
||||
} |
||||
|
||||
@Nested |
||||
class SessionAttributesTests { |
||||
|
||||
@Test |
||||
void sessionAttributesAreCopied() { |
||||
Map<String, Object> map = new LinkedHashMap<>(); |
||||
map.put("one", 1); |
||||
map.put("two", 2); |
||||
assertThat(createRequest(map)).sessionAttributes() |
||||
.containsExactly(entry("one", 1), entry("two", 2)); |
||||
} |
||||
|
||||
@Test |
||||
void sessionAttributesWithWrongKey() { |
||||
HttpServletRequest request = createRequest(Map.of("one", 1)); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(request).sessionAttributes().containsKey("two")) |
||||
.withMessageContainingAll("Session Attributes", "two", "one"); |
||||
} |
||||
|
||||
|
||||
private HttpServletRequest createRequest(Map<String, Object> attributes) { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
HttpSession session = request.getSession(); |
||||
Assertions.assertThat(session).isNotNull(); |
||||
attributes.forEach(session::setAttribute); |
||||
return request; |
||||
} |
||||
|
||||
} |
||||
|
||||
@Test |
||||
void hasAsyncStartedTrue() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.setAsyncStarted(true); |
||||
assertThat(request).hasAsyncStarted(true); |
||||
} |
||||
|
||||
@Test |
||||
void hasAsyncStartedTrueWithFalse() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.setAsyncStarted(false); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(request).hasAsyncStarted(true)) |
||||
.withMessage("Async expected to have started"); |
||||
} |
||||
|
||||
@Test |
||||
void hasAsyncStartedFalse() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.setAsyncStarted(false); |
||||
assertThat(request).hasAsyncStarted(false); |
||||
} |
||||
|
||||
@Test |
||||
void hasAsyncStartedFalseWithTrue() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
request.setAsyncStarted(true); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(request).hasAsyncStarted(false)) |
||||
.withMessage("Async expected to not have started"); |
||||
|
||||
} |
||||
|
||||
private static ResponseAssert assertThat(HttpServletRequest response) { |
||||
return new ResponseAssert(response); |
||||
} |
||||
|
||||
|
||||
private static final class ResponseAssert extends AbstractHttpServletRequestAssert<ResponseAssert, HttpServletRequest> { |
||||
|
||||
ResponseAssert(HttpServletRequest actual) { |
||||
super(actual, ResponseAssert.class); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,138 @@
@@ -0,0 +1,138 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.util.Map; |
||||
|
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
import org.junit.jupiter.api.Nested; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.mock.web.MockHttpServletResponse; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
|
||||
/** |
||||
* Tests for {@link AbstractHttpServletResponseAssert}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class AbstractHttpServletResponseAssertTests { |
||||
|
||||
@Nested |
||||
class HeadersTests { |
||||
|
||||
@Test |
||||
void headersAreMatching() { |
||||
MockHttpServletResponse response = createResponse(Map.of("n1", "v1", "n2", "v2", "n3", "v3")); |
||||
assertThat(response).headers().containsHeaders("n1", "n2", "n3"); |
||||
} |
||||
|
||||
|
||||
private MockHttpServletResponse createResponse(Map<String, String> headers) { |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
headers.forEach(response::addHeader); |
||||
return response; |
||||
} |
||||
} |
||||
|
||||
|
||||
@Nested |
||||
class StatusTests { |
||||
|
||||
@Test |
||||
void hasStatusWithCode() { |
||||
assertThat(createResponse(200)).hasStatus(200); |
||||
} |
||||
|
||||
@Test |
||||
void hasStatusWithHttpStatus() { |
||||
assertThat(createResponse(200)).hasStatus(HttpStatus.OK); |
||||
} |
||||
|
||||
@Test |
||||
void hasStatusOK() { |
||||
assertThat(createResponse(200)).hasStatusOk(); |
||||
} |
||||
|
||||
@Test |
||||
void hasStatusWithWrongCode() { |
||||
MockHttpServletResponse response = createResponse(200); |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertThat(response).hasStatus(300)) |
||||
.withMessageContainingAll("HTTP status code", "200", "300"); |
||||
} |
||||
|
||||
@Test |
||||
void hasStatus1xxInformational() { |
||||
assertThat(createResponse(199)).hasStatus1xxInformational(); |
||||
} |
||||
|
||||
@Test |
||||
void hasStatus2xxSuccessful() { |
||||
assertThat(createResponse(299)).hasStatus2xxSuccessful(); |
||||
} |
||||
|
||||
@Test |
||||
void hasStatus3xxRedirection() { |
||||
assertThat(createResponse(399)).hasStatus3xxRedirection(); |
||||
} |
||||
|
||||
@Test |
||||
void hasStatus4xxClientError() { |
||||
assertThat(createResponse(499)).hasStatus4xxClientError(); |
||||
} |
||||
|
||||
@Test |
||||
void hasStatus5xxServerError() { |
||||
assertThat(createResponse(599)).hasStatus5xxServerError(); |
||||
} |
||||
|
||||
@Test |
||||
void hasStatusWithWrongSeries() { |
||||
MockHttpServletResponse response = createResponse(500); |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(response).hasStatus2xxSuccessful()) |
||||
.withMessageContainingAll("HTTP status series", "SUCCESSFUL", "SERVER_ERROR"); |
||||
} |
||||
|
||||
private MockHttpServletResponse createResponse(int status) { |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
response.setStatus(status); |
||||
return response; |
||||
} |
||||
} |
||||
|
||||
private static ResponseAssert assertThat(HttpServletResponse response) { |
||||
return new ResponseAssert(response); |
||||
} |
||||
|
||||
|
||||
private static final class ResponseAssert extends AbstractHttpServletResponseAssert<HttpServletResponse, ResponseAssert, HttpServletResponse> { |
||||
|
||||
ResponseAssert(HttpServletResponse actual) { |
||||
super(actual, ResponseAssert.class); |
||||
} |
||||
|
||||
@Override |
||||
protected HttpServletResponse getResponse() { |
||||
return this.actual; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,48 @@
@@ -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.web.servlet.assertj; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
|
||||
/** |
||||
* Tests for {@link AbstractMockHttpServletRequestAssert}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class AbstractMockHttpServletRequestAssertTests { |
||||
|
||||
@Test |
||||
void requestCanBeAsserted() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
assertThat(request).satisfies(actual -> assertThat(actual).isSameAs(request)); |
||||
} |
||||
|
||||
|
||||
private static RequestAssert assertThat(MockHttpServletRequest request) { |
||||
return new RequestAssert(request); |
||||
} |
||||
|
||||
private static final class RequestAssert extends AbstractMockHttpServletRequestAssert<RequestAssert> { |
||||
|
||||
RequestAssert(MockHttpServletRequest actual) { |
||||
super(actual, RequestAssert.class); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,106 @@
@@ -0,0 +1,106 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
|
||||
import java.io.UnsupportedEncodingException; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
import org.assertj.core.api.Assertions; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.mock.web.MockHttpServletResponse; |
||||
|
||||
/** |
||||
* Tests for {@link AbstractMockHttpServletResponseAssert}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
public class AbstractMockHttpServletResponseAssertTests { |
||||
|
||||
@Test |
||||
void hasForwardedUrl() { |
||||
String forwardedUrl = "https://example.com/42"; |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
response.setForwardedUrl(forwardedUrl); |
||||
assertThat(response).hasForwardedUrl(forwardedUrl); |
||||
} |
||||
|
||||
@Test |
||||
void hasForwardedUrlWithWrongValue() { |
||||
String forwardedUrl = "https://example.com/42"; |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
response.setForwardedUrl(forwardedUrl); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(response).hasForwardedUrl("another")) |
||||
.withMessageContainingAll("Forwarded URL", forwardedUrl, "another"); |
||||
} |
||||
|
||||
@Test |
||||
void hasRedirectedUrl() { |
||||
String redirectedUrl = "https://example.com/42"; |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
response.addHeader(HttpHeaders.LOCATION, redirectedUrl); |
||||
assertThat(response).hasRedirectedUrl(redirectedUrl); |
||||
} |
||||
|
||||
@Test |
||||
void hasRedirectedUrlWithWrongValue() { |
||||
String redirectedUrl = "https://example.com/42"; |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
response.addHeader(HttpHeaders.LOCATION, redirectedUrl); |
||||
Assertions.assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(response).hasRedirectedUrl("another")) |
||||
.withMessageContainingAll("Redirected URL", redirectedUrl, "another"); |
||||
} |
||||
|
||||
@Test |
||||
void bodyHasContent() throws UnsupportedEncodingException { |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
response.getWriter().write("OK"); |
||||
assertThat(response).body().asString().isEqualTo("OK"); |
||||
} |
||||
|
||||
@Test |
||||
void bodyHasContentWithResponseCharacterEncoding() throws UnsupportedEncodingException { |
||||
byte[] bytes = "OK".getBytes(StandardCharsets.UTF_8); |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
response.getWriter().write("OK"); |
||||
response.setContentType(StandardCharsets.UTF_8.name()); |
||||
assertThat(response).body().isEqualTo(bytes); |
||||
} |
||||
|
||||
|
||||
private static ResponseAssert assertThat(MockHttpServletResponse response) { |
||||
return new ResponseAssert(response); |
||||
} |
||||
|
||||
|
||||
private static final class ResponseAssert extends AbstractMockHttpServletResponseAssert<ResponseAssert, MockHttpServletResponse> { |
||||
|
||||
ResponseAssert(MockHttpServletResponse actual) { |
||||
super(null, actual, ResponseAssert.class); |
||||
} |
||||
|
||||
@Override |
||||
protected MockHttpServletResponse getResponse() { |
||||
return this.actual; |
||||
} |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,558 @@
@@ -0,0 +1,558 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.List; |
||||
import java.util.Locale; |
||||
import java.util.Map; |
||||
import java.util.concurrent.Callable; |
||||
import java.util.concurrent.atomic.AtomicBoolean; |
||||
import java.util.function.Consumer; |
||||
|
||||
import jakarta.servlet.ServletException; |
||||
import jakarta.servlet.http.Cookie; |
||||
import jakarta.servlet.http.HttpServletRequest; |
||||
import jakarta.servlet.http.HttpServletResponse; |
||||
import jakarta.validation.Valid; |
||||
import jakarta.validation.constraints.Size; |
||||
import org.junit.jupiter.api.Nested; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.context.annotation.Import; |
||||
import org.springframework.core.io.ClassPathResource; |
||||
import org.springframework.http.HttpHeaders; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.stereotype.Controller; |
||||
import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; |
||||
import org.springframework.test.context.web.WebAppConfiguration; |
||||
import org.springframework.test.web.Person; |
||||
import org.springframework.test.web.servlet.MvcResult; |
||||
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; |
||||
import org.springframework.ui.Model; |
||||
import org.springframework.validation.Errors; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.bind.annotation.ModelAttribute; |
||||
import org.springframework.web.bind.annotation.PathVariable; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RequestMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
import org.springframework.web.bind.annotation.SessionAttributes; |
||||
import org.springframework.web.context.WebApplicationContext; |
||||
import org.springframework.web.servlet.DispatcherServlet; |
||||
import org.springframework.web.servlet.HandlerInterceptor; |
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc; |
||||
import org.springframework.web.servlet.mvc.support.RedirectAttributes; |
||||
|
||||
import static java.util.Map.entry; |
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.assertj.core.api.InstanceOfAssertFactories.map; |
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; |
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; |
||||
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; |
||||
|
||||
/** |
||||
* Integration tests for {@link AssertableMockMvc}. |
||||
* |
||||
* @author Brian Clozel |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
@SpringJUnitConfig |
||||
@WebAppConfiguration |
||||
public class AssertableMockMvcIntegrationTests { |
||||
|
||||
private final AssertableMockMvc mockMvc; |
||||
|
||||
AssertableMockMvcIntegrationTests(WebApplicationContext wac) { |
||||
this.mockMvc = AssertableMockMvc.from(wac); |
||||
} |
||||
|
||||
@Nested |
||||
class RequestTests { |
||||
|
||||
@Test |
||||
void hasAsyncStartedTrue() { |
||||
assertThat(perform(get("/callable").accept(MediaType.APPLICATION_JSON))) |
||||
.request().hasAsyncStarted(true); |
||||
} |
||||
|
||||
@Test |
||||
void hasAsyncStartedFalse() { |
||||
assertThat(perform(get("/greet"))).request().hasAsyncStarted(false); |
||||
} |
||||
|
||||
@Test |
||||
void attributes() { |
||||
assertThat(perform(get("/greet"))).request().attributes() |
||||
.containsKey(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE); |
||||
} |
||||
|
||||
@Test |
||||
void sessionAttributes() { |
||||
assertThat(perform(get("/locale"))).request().sessionAttributes() |
||||
.containsOnly(entry("locale", Locale.UK)); |
||||
} |
||||
} |
||||
|
||||
@Nested |
||||
class CookieTests { |
||||
|
||||
@Test |
||||
void containsCookie() { |
||||
Cookie cookie = new Cookie("test", "value"); |
||||
assertThat(performWithCookie(cookie, get("/greet"))).cookies().containsCookie("test"); |
||||
} |
||||
|
||||
@Test |
||||
void hasValue() { |
||||
Cookie cookie = new Cookie("test", "value"); |
||||
assertThat(performWithCookie(cookie, get("/greet"))).cookies().hasValue("test", "value"); |
||||
} |
||||
|
||||
private AssertableMvcResult performWithCookie(Cookie cookie, MockHttpServletRequestBuilder request) { |
||||
AssertableMockMvc mockMvc = AssertableMockMvc.of(List.of(new TestController()), builder -> builder.addInterceptors( |
||||
new HandlerInterceptor() { |
||||
@Override |
||||
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { |
||||
response.addCookie(cookie); |
||||
return true; |
||||
} |
||||
}).build()); |
||||
return mockMvc.perform(request); |
||||
} |
||||
} |
||||
|
||||
@Nested |
||||
class ContentTypeTests { |
||||
|
||||
@Test |
||||
void contentType() { |
||||
assertThat(perform(get("/greet"))).contentType().isCompatibleWith("text/plain"); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Nested |
||||
class StatusTests { |
||||
|
||||
@Test |
||||
void statusOk() { |
||||
assertThat(perform(get("/greet"))).hasStatusOk(); |
||||
} |
||||
|
||||
@Test |
||||
void statusSeries() { |
||||
assertThat(perform(get("/greet"))).hasStatus2xxSuccessful(); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Nested |
||||
class HeadersTests { |
||||
|
||||
@Test |
||||
void shouldAssertHeader() { |
||||
assertThat(perform(get("/greet"))).headers() |
||||
.hasValue("Content-Type", "text/plain;charset=ISO-8859-1"); |
||||
} |
||||
|
||||
@Test |
||||
void shouldAssertHeaderWithCallback() { |
||||
assertThat(perform(get("/greet"))).headers().satisfies(textContent("ISO-8859-1")); |
||||
} |
||||
|
||||
private Consumer<HttpHeaders> textContent(String charset) { |
||||
return headers -> assertThat(headers).containsEntry( |
||||
"Content-Type", List.of("text/plain;charset=%s".formatted(charset))); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Nested |
||||
class ModelAndViewTests { |
||||
|
||||
@Test |
||||
void hasViewName() { |
||||
assertThat(perform(get("/persons/{0}", "Andy"))).hasViewName("persons/index"); |
||||
} |
||||
|
||||
@Test |
||||
void viewNameWithCustomAssertion() { |
||||
assertThat(perform(get("/persons/{0}", "Andy"))).viewName().startsWith("persons"); |
||||
} |
||||
|
||||
@Test |
||||
void containsAttributes() { |
||||
assertThat(perform(post("/persons").param("name", "Andy"))).model() |
||||
.containsOnlyKeys("name").containsEntry("name", "Andy"); |
||||
} |
||||
|
||||
@Test |
||||
void hasErrors() { |
||||
assertThat(perform(post("/persons"))).model().hasErrors(); |
||||
} |
||||
|
||||
@Test |
||||
void hasAttributeErrors() { |
||||
assertThat(perform(post("/persons"))).model().hasAttributeErrors("person"); |
||||
} |
||||
|
||||
@Test |
||||
void hasAttributeErrorsCount() { |
||||
assertThat(perform(post("/persons"))).model().extractingBindingResult("person").hasErrorsCount(1); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Nested |
||||
class FlashTests { |
||||
|
||||
@Test |
||||
void containsAttributes() { |
||||
assertThat(perform(post("/persons").param("name", "Andy"))).flash() |
||||
.containsOnlyKeys("message").hasEntrySatisfying("message", |
||||
value -> assertThat(value).isInstanceOfSatisfying(String.class, |
||||
stringValue -> assertThat(stringValue).startsWith("success"))); |
||||
} |
||||
} |
||||
|
||||
@Nested |
||||
class BodyTests { |
||||
|
||||
@Test |
||||
void asyncResult() { |
||||
assertThat(perform(get("/callable").accept(MediaType.APPLICATION_JSON))) |
||||
.asyncResult().asInstanceOf(map(String.class, Object.class)) |
||||
.containsOnly(entry("key", "value")); |
||||
} |
||||
|
||||
@Test |
||||
void stringContent() { |
||||
assertThat(perform(get("/greet"))).body().asString().isEqualTo("hello"); |
||||
} |
||||
|
||||
@Test |
||||
void jsonPathContent() { |
||||
assertThat(perform(get("/message"))).body().jsonPath() |
||||
.extractingPath("$.message").asString().isEqualTo("hello"); |
||||
} |
||||
|
||||
@Test |
||||
void jsonContentCanLoadResourceFromClasspath() { |
||||
assertThat(perform(get("/message"))).body().json().isLenientlyEqualTo( |
||||
new ClassPathResource("message.json", AssertableMockMvcIntegrationTests.class)); |
||||
} |
||||
|
||||
@Test |
||||
void jsonContentUsingResourceLoaderClass() { |
||||
assertThat(perform(get("/message"))).body().json(AssertableMockMvcIntegrationTests.class) |
||||
.isLenientlyEqualTo("message.json"); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Nested |
||||
class HandlerTests { |
||||
|
||||
@Test |
||||
void handlerOn404() { |
||||
assertThat(perform(get("/unknown-resource"))).handler().isNull(); |
||||
} |
||||
|
||||
@Test |
||||
void hasType() { |
||||
assertThat(perform(get("/greet"))).handler().hasType(TestController.class); |
||||
} |
||||
|
||||
@Test |
||||
void isMethodHandler() { |
||||
assertThat(perform(get("/greet"))).handler().isMethodHandler(); |
||||
} |
||||
|
||||
@Test |
||||
void isInvokedOn() { |
||||
assertThat(perform(get("/callable"))).handler() |
||||
.isInvokedOn(AsyncController.class, AsyncController::getCallable); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Nested |
||||
class ExceptionTests { |
||||
|
||||
@Test |
||||
void doesNotHaveUnresolvedException() { |
||||
assertThat(perform(get("/greet"))).doesNotHaveUnresolvedException(); |
||||
} |
||||
|
||||
@Test |
||||
void hasUnresolvedException() { |
||||
assertThat(perform(get("/error/1"))).hasUnresolvedException(); |
||||
} |
||||
|
||||
@Test |
||||
void doesNotHaveUnresolvedExceptionWithUnresolvedException() { |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> |
||||
assertThat(perform(get("/error/1"))).doesNotHaveUnresolvedException()) |
||||
.withMessage("Expecting request to have succeeded but it has failed"); |
||||
} |
||||
|
||||
@Test |
||||
void hasUnresolvedExceptionWithoutUnresolvedException() { |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> |
||||
assertThat(perform(get("/greet"))).hasUnresolvedException()) |
||||
.withMessage("Expecting request to have failed but it has succeeded"); |
||||
} |
||||
|
||||
@Test |
||||
void unresolvedExceptionWithFailedRequest() { |
||||
assertThat(perform(get("/error/1"))).unresolvedException() |
||||
.isInstanceOf(ServletException.class) |
||||
.cause().isInstanceOf(IllegalStateException.class).hasMessage("Expected"); |
||||
} |
||||
|
||||
@Test |
||||
void unresolvedExceptionWithSuccessfulRequest() { |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> |
||||
assertThat(perform(get("/greet"))).unresolvedException()) |
||||
.withMessage("Expecting request to have failed but it has succeeded"); |
||||
} |
||||
|
||||
// Check that assertions fail immediately if request has failed with unresolved exception
|
||||
|
||||
@Test |
||||
void assertAndApplyWithUnresolvedException() { |
||||
testAssertionFailureWithUnresolvableException( |
||||
result -> assertThat(result).apply(mvcResult -> {})); |
||||
} |
||||
|
||||
@Test |
||||
void assertAsyncResultWithUnresolvedException() { |
||||
testAssertionFailureWithUnresolvableException( |
||||
result -> assertThat(result).asyncResult()); |
||||
} |
||||
|
||||
@Test |
||||
void assertContentTypeWithUnresolvedException() { |
||||
testAssertionFailureWithUnresolvableException( |
||||
result -> assertThat(result).contentType()); |
||||
} |
||||
|
||||
@Test |
||||
void assertCookiesWithUnresolvedException() { |
||||
testAssertionFailureWithUnresolvableException( |
||||
result -> assertThat(result).cookies()); |
||||
} |
||||
|
||||
@Test |
||||
void assertFlashWithUnresolvedException() { |
||||
testAssertionFailureWithUnresolvableException( |
||||
result -> assertThat(result).flash()); |
||||
} |
||||
|
||||
@Test |
||||
void assertStatusWithUnresolvedException() { |
||||
testAssertionFailureWithUnresolvableException( |
||||
result -> assertThat(result).hasStatus(3)); |
||||
} |
||||
|
||||
@Test |
||||
void assertHeaderWithUnresolvedException() { |
||||
testAssertionFailureWithUnresolvableException( |
||||
result -> assertThat(result).headers()); |
||||
} |
||||
|
||||
@Test |
||||
void assertViewNameWithUnresolvedException() { |
||||
testAssertionFailureWithUnresolvableException( |
||||
result -> assertThat(result).hasViewName("test")); |
||||
} |
||||
|
||||
@Test |
||||
void assertForwardedUrlWithUnresolvedException() { |
||||
testAssertionFailureWithUnresolvableException( |
||||
result -> assertThat(result).hasForwardedUrl("test")); |
||||
} |
||||
|
||||
@Test |
||||
void assertRedirectedUrlWithUnresolvedException() { |
||||
testAssertionFailureWithUnresolvableException( |
||||
result -> assertThat(result).hasRedirectedUrl("test")); |
||||
} |
||||
|
||||
@Test |
||||
void assertRequestWithUnresolvedException() { |
||||
testAssertionFailureWithUnresolvableException( |
||||
result -> assertThat(result).request()); |
||||
} |
||||
|
||||
@Test |
||||
void assertModelWithUnresolvedException() { |
||||
testAssertionFailureWithUnresolvableException( |
||||
result -> assertThat(result).model()); |
||||
} |
||||
|
||||
@Test |
||||
void assertBodyWithUnresolvedException() { |
||||
testAssertionFailureWithUnresolvableException( |
||||
result -> assertThat(result).body()); |
||||
} |
||||
|
||||
|
||||
private void testAssertionFailureWithUnresolvableException(Consumer<AssertableMvcResult> assertions) { |
||||
AssertableMvcResult result = perform(get("/error/1")); |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertions.accept(result)) |
||||
.withMessageContainingAll("Request has failed unexpectedly:", |
||||
ServletException.class.getName(), IllegalStateException.class.getName(), |
||||
"Expected"); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Test |
||||
void hasForwardUrl() { |
||||
assertThat(perform(get("/persons/John"))).hasForwardedUrl("persons/index"); |
||||
} |
||||
|
||||
@Test |
||||
void hasRedirectUrl() { |
||||
assertThat(perform(post("/persons").param("name", "Andy"))).hasStatus(HttpStatus.FOUND) |
||||
.hasRedirectedUrl("/persons/Andy"); |
||||
} |
||||
|
||||
@Test |
||||
void satisfiesAllowAdditionalAssertions() { |
||||
assertThat(this.mockMvc.perform(get("/greet"))).satisfies(result -> { |
||||
assertThat(result).isInstanceOf(MvcResult.class); |
||||
assertThat(result).hasStatusOk(); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void resultMatcherCanBeReused() { |
||||
assertThat(this.mockMvc.perform(get("/greet"))).matches(status().isOk()); |
||||
} |
||||
|
||||
@Test |
||||
void resultMatcherFailsWithDedicatedException() { |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(this.mockMvc.perform(get("/greet"))) |
||||
.matches(status().isNotFound())) |
||||
.withMessageContaining("Status expected:<404> but was:<200>"); |
||||
} |
||||
|
||||
@Test |
||||
void shouldApplyResultHandler() { // Spring RESTDocs example
|
||||
AtomicBoolean applied = new AtomicBoolean(); |
||||
assertThat(this.mockMvc.perform(get("/greet"))).apply(result -> applied.set(true)); |
||||
assertThat(applied).isTrue(); |
||||
} |
||||
|
||||
|
||||
private AssertableMvcResult perform(MockHttpServletRequestBuilder builder) { |
||||
return this.mockMvc.perform(builder); |
||||
} |
||||
|
||||
|
||||
@Configuration |
||||
@EnableWebMvc |
||||
@Import({ TestController.class, PersonController.class, AsyncController.class, |
||||
SessionController.class, ErrorController.class }) |
||||
static class WebConfiguration { |
||||
|
||||
} |
||||
|
||||
@RestController |
||||
static class TestController { |
||||
|
||||
@GetMapping(path = "/greet", produces = "text/plain") |
||||
String greet() { |
||||
return "hello"; |
||||
} |
||||
|
||||
@GetMapping(path = "/message", produces = MediaType.APPLICATION_JSON_VALUE) |
||||
String message() { |
||||
return "{\"message\": \"hello\"}"; |
||||
} |
||||
} |
||||
|
||||
@Controller |
||||
@RequestMapping("/persons") |
||||
static class PersonController { |
||||
|
||||
@GetMapping("/{name}") |
||||
public String get(@PathVariable String name, Model model) { |
||||
model.addAttribute(new Person(name)); |
||||
return "persons/index"; |
||||
} |
||||
|
||||
@PostMapping |
||||
String create(@Valid Person person, Errors errors, RedirectAttributes redirectAttrs) { |
||||
if (errors.hasErrors()) { |
||||
return "persons/add"; |
||||
} |
||||
redirectAttrs.addAttribute("name", person.getName()); |
||||
redirectAttrs.addFlashAttribute("message", "success!"); |
||||
return "redirect:/persons/{name}"; |
||||
} |
||||
} |
||||
|
||||
|
||||
@RestController |
||||
static class AsyncController { |
||||
|
||||
@GetMapping("/callable") |
||||
public Callable<Map<String, String>> getCallable() { |
||||
return () -> Collections.singletonMap("key", "value"); |
||||
} |
||||
} |
||||
|
||||
@Controller |
||||
@SessionAttributes("locale") |
||||
private static class SessionController { |
||||
|
||||
@ModelAttribute |
||||
void populate(Model model) { |
||||
model.addAttribute("locale", Locale.UK); |
||||
} |
||||
|
||||
@RequestMapping("/locale") |
||||
String handle() { |
||||
return "view"; |
||||
} |
||||
} |
||||
|
||||
@Controller |
||||
private static class ErrorController { |
||||
|
||||
@GetMapping("/error/1") |
||||
public String one() { |
||||
throw new IllegalStateException("Expected"); |
||||
} |
||||
|
||||
@GetMapping("/error/validation/{id}") |
||||
public String validation(@PathVariable @Size(max = 4) String id) { |
||||
return "Hello " + id; |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,210 @@
@@ -0,0 +1,210 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.concurrent.atomic.AtomicInteger; |
||||
|
||||
import com.fasterxml.jackson.databind.ObjectMapper; |
||||
import jakarta.servlet.ServletException; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.context.annotation.AnnotationConfigUtils; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Configuration; |
||||
import org.springframework.http.HttpStatus; |
||||
import org.springframework.http.MediaType; |
||||
import org.springframework.http.converter.GenericHttpMessageConverter; |
||||
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter; |
||||
import org.springframework.mock.web.MockServletContext; |
||||
import org.springframework.test.json.JsonPathAssert; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
import org.springframework.web.context.support.GenericWebApplicationContext; |
||||
import org.springframework.web.servlet.config.annotation.EnableWebMvc; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
import static org.assertj.core.api.Assertions.assertThatThrownBy; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.spy; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; |
||||
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; |
||||
|
||||
/** |
||||
* Tests for {@link AssertableMockMvc}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class AssertableMockMvcTests { |
||||
|
||||
private static final MappingJackson2HttpMessageConverter jsonHttpMessageConverter = |
||||
new MappingJackson2HttpMessageConverter(new ObjectMapper()); |
||||
|
||||
@Test |
||||
void createShouldRejectNullMockMvc() { |
||||
assertThatThrownBy(() -> AssertableMockMvc.create(null)) |
||||
.isInstanceOf(IllegalArgumentException.class); |
||||
} |
||||
|
||||
@Test |
||||
void createWithExistingWebApplicationContext() { |
||||
try (GenericWebApplicationContext wac = create(WebConfiguration.class)) { |
||||
AssertableMockMvc mockMvc = AssertableMockMvc.from(wac); |
||||
assertThat(mockMvc.perform(post("/increase"))).body().isEqualTo("counter 41"); |
||||
assertThat(mockMvc.perform(post("/increase"))).body().isEqualTo("counter 42"); |
||||
} |
||||
} |
||||
|
||||
@Test |
||||
void createWithControllerClassShouldInstantiateControllers() { |
||||
AssertableMockMvc mockMvc = AssertableMockMvc.of(HelloController.class, CounterController.class); |
||||
assertThat(mockMvc.perform(get("/hello"))).body().isEqualTo("Hello World"); |
||||
assertThat(mockMvc.perform(post("/increase"))).body().isEqualTo("counter 1"); |
||||
assertThat(mockMvc.perform(post("/increase"))).body().isEqualTo("counter 2"); |
||||
} |
||||
|
||||
@Test |
||||
void createWithControllersShouldUseThemAsIs() { |
||||
AssertableMockMvc mockMvc = AssertableMockMvc.of(new HelloController(), |
||||
new CounterController(new AtomicInteger(41))); |
||||
assertThat(mockMvc.perform(get("/hello"))).body().isEqualTo("Hello World"); |
||||
assertThat(mockMvc.perform(post("/increase"))).body().isEqualTo("counter 42"); |
||||
assertThat(mockMvc.perform(post("/increase"))).body().isEqualTo("counter 43"); |
||||
} |
||||
|
||||
@Test |
||||
void createWithControllerAndCustomizations() { |
||||
AssertableMockMvc mockMvc = AssertableMockMvc.of(List.of(new HelloController()), builder -> |
||||
builder.defaultRequest(get("/hello").accept(MediaType.APPLICATION_JSON)).build()); |
||||
assertThat(mockMvc.perform(get("/hello"))).hasStatus(HttpStatus.NOT_ACCEPTABLE); |
||||
} |
||||
|
||||
@Test |
||||
void createWithControllersHasNoHttpMessageConverter() { |
||||
AssertableMockMvc mockMvc = AssertableMockMvc.of(new HelloController()); |
||||
JsonPathAssert jsonPathAssert = assertThat(mockMvc.perform(get("/json"))).hasStatusOk().body().jsonPath(); |
||||
assertThatIllegalStateException() |
||||
.isThrownBy(() -> jsonPathAssert.extractingPath("$").convertTo(Message.class)) |
||||
.withMessageContaining("No JSON message converter available"); |
||||
} |
||||
|
||||
@Test |
||||
void createWithControllerCanConfigureHttpMessageConverters() { |
||||
AssertableMockMvc mockMvc = AssertableMockMvc.of(HelloController.class) |
||||
.withHttpMessageConverters(List.of(jsonHttpMessageConverter)); |
||||
assertThat(mockMvc.perform(get("/json"))).hasStatusOk().body().jsonPath() |
||||
.extractingPath("$").convertTo(Message.class).satisfies(message -> { |
||||
assertThat(message.message()).isEqualTo("Hello World"); |
||||
assertThat(message.counter()).isEqualTo(42); |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
@SuppressWarnings("unchecked") |
||||
void withHttpMessageConverterDetectsJsonConverter() { |
||||
MappingJackson2HttpMessageConverter converter = spy(jsonHttpMessageConverter); |
||||
AssertableMockMvc mockMvc = AssertableMockMvc.of(HelloController.class) |
||||
.withHttpMessageConverters(List.of(mock(GenericHttpMessageConverter.class), |
||||
mock(GenericHttpMessageConverter.class), converter)); |
||||
assertThat(mockMvc.perform(get("/json"))).hasStatusOk().body().jsonPath() |
||||
.extractingPath("$").convertTo(Message.class).satisfies(message -> { |
||||
assertThat(message.message()).isEqualTo("Hello World"); |
||||
assertThat(message.counter()).isEqualTo(42); |
||||
}); |
||||
verify(converter).canWrite(Map.class, MediaType.APPLICATION_JSON); |
||||
} |
||||
|
||||
@Test |
||||
void performWithUnresolvedExceptionSetsException() { |
||||
AssertableMockMvc mockMvc = AssertableMockMvc.of(HelloController.class); |
||||
AssertableMvcResult result = mockMvc.perform(get("/error")); |
||||
assertThat(result.getUnresolvedException()).isNotNull().isInstanceOf(ServletException.class) |
||||
.cause().isInstanceOf(IllegalStateException.class).hasMessage("Expected"); |
||||
assertThat(result).hasFieldOrPropertyWithValue("target", null); |
||||
} |
||||
|
||||
private GenericWebApplicationContext create(Class<?>... classes) { |
||||
GenericWebApplicationContext applicationContext = new GenericWebApplicationContext( |
||||
new MockServletContext()); |
||||
AnnotationConfigUtils.registerAnnotationConfigProcessors(applicationContext); |
||||
for (Class<?> beanClass : classes) { |
||||
applicationContext.registerBean(beanClass); |
||||
} |
||||
applicationContext.refresh(); |
||||
return applicationContext; |
||||
} |
||||
|
||||
@Configuration(proxyBeanMethods = false) |
||||
@EnableWebMvc |
||||
static class WebConfiguration { |
||||
|
||||
@Bean |
||||
CounterController counterController() { |
||||
return new CounterController(new AtomicInteger(40)); |
||||
} |
||||
} |
||||
|
||||
|
||||
@RestController |
||||
private static class HelloController { |
||||
|
||||
@GetMapping(path = "/hello", produces = "text/plain") |
||||
public String hello() { |
||||
return "Hello World"; |
||||
} |
||||
|
||||
@GetMapping("/error") |
||||
public String error() { |
||||
throw new IllegalStateException("Expected"); |
||||
} |
||||
|
||||
@GetMapping(path = "/json", produces = "application/json") |
||||
public String json() { |
||||
return """ |
||||
{ |
||||
"message": "Hello World", |
||||
"counter": 42 |
||||
}"""; |
||||
} |
||||
} |
||||
|
||||
private record Message(String message, int counter) {} |
||||
|
||||
@RestController |
||||
private static class CounterController { |
||||
|
||||
private final AtomicInteger counter; |
||||
|
||||
public CounterController(AtomicInteger counter) { |
||||
this.counter = counter; |
||||
} |
||||
|
||||
public CounterController() { |
||||
this(new AtomicInteger()); |
||||
} |
||||
|
||||
@PostMapping("/increase") |
||||
public String increase() { |
||||
int value = this.counter.incrementAndGet(); |
||||
return "counter " + value; |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,182 @@
@@ -0,0 +1,182 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
|
||||
import java.time.Duration; |
||||
import java.util.List; |
||||
|
||||
import jakarta.servlet.http.Cookie; |
||||
import org.assertj.core.api.AssertProvider; |
||||
import org.junit.jupiter.api.BeforeAll; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
|
||||
/** |
||||
* Tests for {@link CookieMapAssert}. |
||||
* |
||||
* @author Brian Clozel |
||||
*/ |
||||
class CookieMapAssertTests { |
||||
|
||||
static Cookie[] cookies; |
||||
|
||||
@BeforeAll |
||||
static void setup() { |
||||
Cookie framework = new Cookie("framework", "spring"); |
||||
framework.setSecure(true); |
||||
framework.setHttpOnly(true); |
||||
Cookie age = new Cookie("age", "value"); |
||||
age.setMaxAge(1200); |
||||
Cookie domain = new Cookie("domain", "value"); |
||||
domain.setDomain("spring.io"); |
||||
Cookie path = new Cookie("path", "value"); |
||||
path.setPath("/spring"); |
||||
cookies = List.of(framework, age, domain, path).toArray(new Cookie[0]); |
||||
} |
||||
|
||||
@Test |
||||
void containsCookieWhenCookieExistsShouldPass() { |
||||
assertThat(forCookies()).containsCookie("framework"); |
||||
} |
||||
|
||||
@Test |
||||
void containsCookieWhenCookieMissingShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> |
||||
assertThat(forCookies()).containsCookie("missing")); |
||||
} |
||||
|
||||
@Test |
||||
void containsCookiesWhenCookiesExistShouldPass() { |
||||
assertThat(forCookies()).containsCookies("framework", "age"); |
||||
} |
||||
|
||||
@Test |
||||
void containsCookiesWhenCookieMissingShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> |
||||
assertThat(forCookies()).containsCookies("framework", "missing")); |
||||
} |
||||
|
||||
@Test |
||||
void doesNotContainCookieWhenCookieMissingShouldPass() { |
||||
assertThat(forCookies()).doesNotContainCookie("missing"); |
||||
} |
||||
|
||||
@Test |
||||
void doesNotContainCookieWhenCookieExistsShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> |
||||
assertThat(forCookies()).doesNotContainCookie("framework")); |
||||
} |
||||
|
||||
@Test |
||||
void doesNotContainCookiesWhenCookiesMissingShouldPass() { |
||||
assertThat(forCookies()).doesNotContainCookies("missing", "missing2"); |
||||
} |
||||
|
||||
@Test |
||||
void doesNotContainCookiesWhenAtLeastOneCookieExistShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> |
||||
assertThat(forCookies()).doesNotContainCookies("missing", "framework")); |
||||
} |
||||
|
||||
@Test |
||||
void hasValueEqualsWhenCookieValueMatchesShouldPass() { |
||||
assertThat(forCookies()).hasValue("framework", "spring"); |
||||
} |
||||
|
||||
@Test |
||||
void hasValueEqualsWhenCookieValueDiffersShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> |
||||
assertThat(forCookies()).hasValue("framework", "other")); |
||||
} |
||||
|
||||
@Test |
||||
void hasCookieSatisfyingWhenCookieValueMatchesShouldPass() { |
||||
assertThat(forCookies()).hasCookieSatisfying("framework", cookie -> |
||||
assertThat(cookie.getValue()).startsWith("spr")); |
||||
} |
||||
|
||||
@Test |
||||
void hasCookieSatisfyingWhenCookieValueDiffersShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> |
||||
assertThat(forCookies()).hasCookieSatisfying("framework", cookie -> |
||||
assertThat(cookie.getValue()).startsWith("not"))); |
||||
} |
||||
|
||||
@Test |
||||
void hasMaxAgeWhenCookieAgeMatchesShouldPass() { |
||||
assertThat(forCookies()).hasMaxAge("age", Duration.ofMinutes(20)); |
||||
} |
||||
|
||||
@Test |
||||
void hasMaxAgeWhenCookieAgeDiffersShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> |
||||
assertThat(forCookies()).hasMaxAge("age", Duration.ofMinutes(30))); |
||||
} |
||||
|
||||
@Test |
||||
void pathWhenCookiePathMatchesShouldPass() { |
||||
assertThat(forCookies()).hasPath("path", "/spring"); |
||||
} |
||||
|
||||
@Test |
||||
void pathWhenCookiePathDiffersShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> |
||||
assertThat(forCookies()).hasPath("path", "/other")); |
||||
} |
||||
|
||||
@Test |
||||
void hasDomainWhenCookieDomainMatchesShouldPass() { |
||||
assertThat(forCookies()).hasDomain("domain", "spring.io"); |
||||
} |
||||
|
||||
@Test |
||||
void hasDomainWhenCookieDomainDiffersShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> |
||||
assertThat(forCookies()).hasDomain("domain", "example.org")); |
||||
} |
||||
|
||||
@Test |
||||
void isSecureWhenCookieSecureMatchesShouldPass() { |
||||
assertThat(forCookies()).isSecure("framework", true); |
||||
} |
||||
|
||||
@Test |
||||
void isSecureWhenCookieSecureDiffersShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> |
||||
assertThat(forCookies()).isSecure("domain", true)); |
||||
} |
||||
|
||||
@Test |
||||
void isHttpOnlyWhenCookieHttpOnlyMatchesShouldPass() { |
||||
assertThat(forCookies()).isHttpOnly("framework", true); |
||||
} |
||||
|
||||
@Test |
||||
void isHttpOnlyWhenCookieHttpOnlyDiffersShouldFail() { |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> |
||||
assertThat(forCookies()).isHttpOnly("domain", true)); |
||||
} |
||||
|
||||
|
||||
private AssertProvider<CookieMapAssert> forCookies() { |
||||
return () -> new CookieMapAssert(cookies); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,107 @@
@@ -0,0 +1,107 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.util.function.Consumer; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.mock.web.MockHttpServletRequest; |
||||
import org.springframework.test.web.servlet.MvcResult; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatIllegalStateException; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
|
||||
/** |
||||
* Tests for {@link DefaultAssertableMvcResult}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class DefaultAssertableMvcResultTests { |
||||
|
||||
@Test |
||||
void createWithMvcResultDelegatesToIt() { |
||||
MockHttpServletRequest request = new MockHttpServletRequest(); |
||||
MvcResult mvcResult = mock(MvcResult.class); |
||||
given(mvcResult.getRequest()).willReturn(request); |
||||
DefaultAssertableMvcResult result = new DefaultAssertableMvcResult(mvcResult, null, null); |
||||
assertThat(result.getRequest()).isSameAs(request); |
||||
verify(mvcResult).getRequest(); |
||||
} |
||||
|
||||
@Test |
||||
void createWithExceptionDoesNotAllowAccessToRequest() { |
||||
assertRequestHasFailed(DefaultAssertableMvcResult::getRequest); |
||||
} |
||||
|
||||
@Test |
||||
void createWithExceptionDoesNotAllowAccessToResponse() { |
||||
assertRequestHasFailed(DefaultAssertableMvcResult::getResponse); |
||||
} |
||||
|
||||
@Test |
||||
void createWithExceptionDoesNotAllowAccessToHandler() { |
||||
assertRequestHasFailed(DefaultAssertableMvcResult::getHandler); |
||||
} |
||||
|
||||
@Test |
||||
void createWithExceptionDoesNotAllowAccessToInterceptors() { |
||||
assertRequestHasFailed(DefaultAssertableMvcResult::getInterceptors); |
||||
} |
||||
|
||||
@Test |
||||
void createWithExceptionDoesNotAllowAccessToModelAndView() { |
||||
assertRequestHasFailed(DefaultAssertableMvcResult::getModelAndView); |
||||
} |
||||
|
||||
@Test |
||||
void createWithExceptionDoesNotAllowAccessToResolvedException() { |
||||
assertRequestHasFailed(DefaultAssertableMvcResult::getResolvedException); |
||||
} |
||||
|
||||
@Test |
||||
void createWithExceptionDoesNotAllowAccessToFlashMap() { |
||||
assertRequestHasFailed(DefaultAssertableMvcResult::getFlashMap); |
||||
} |
||||
|
||||
@Test |
||||
void createWithExceptionDoesNotAllowAccessToAsyncResult() { |
||||
assertRequestHasFailed(DefaultAssertableMvcResult::getAsyncResult); |
||||
} |
||||
|
||||
@Test |
||||
void createWithExceptionDoesNotAllowAccessToAsyncResultWithTimeToWait() { |
||||
assertRequestHasFailed(result -> result.getAsyncResult(1000)); |
||||
} |
||||
|
||||
@Test |
||||
void createWithExceptionReturnsException() { |
||||
IllegalStateException exception = new IllegalStateException("Expected"); |
||||
DefaultAssertableMvcResult result = new DefaultAssertableMvcResult(null, exception, null); |
||||
assertThat(result.getUnresolvedException()).isSameAs(exception); |
||||
} |
||||
|
||||
private void assertRequestHasFailed(Consumer<DefaultAssertableMvcResult> action) { |
||||
DefaultAssertableMvcResult result = new DefaultAssertableMvcResult(null, new IllegalStateException("Expected"), null); |
||||
assertThatIllegalStateException().isThrownBy(() -> action.accept(result)) |
||||
.withMessageContaining("Request has failed with unresolved exception"); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,142 @@
@@ -0,0 +1,142 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.lang.reflect.Method; |
||||
|
||||
import org.assertj.core.api.AssertProvider; |
||||
import org.assertj.core.api.Assertions; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.http.ResponseEntity; |
||||
import org.springframework.util.ReflectionUtils; |
||||
import org.springframework.web.bind.annotation.GetMapping; |
||||
import org.springframework.web.bind.annotation.PostMapping; |
||||
import org.springframework.web.bind.annotation.RestController; |
||||
import org.springframework.web.method.HandlerMethod; |
||||
import org.springframework.web.servlet.resource.DefaultServletHttpRequestHandler; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
|
||||
/** |
||||
* Tests for {@link HandlerResultAssert}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class HandlerResultAssertTests { |
||||
|
||||
@Test |
||||
void hasTypeUseController() { |
||||
assertThat(handlerMethod(new TestController(), "greet")).hasType(TestController.class); |
||||
} |
||||
|
||||
@Test |
||||
void isMethodHandlerWithMethodHandler() { |
||||
assertThat(handlerMethod(new TestController(), "greet")).isMethodHandler(); |
||||
} |
||||
|
||||
@Test |
||||
void isMethodHandlerWithServletHandler() { |
||||
AssertProvider<HandlerResultAssert> actual = handler(new DefaultServletHttpRequestHandler()); |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(actual).isMethodHandler()) |
||||
.withMessageContainingAll(DefaultServletHttpRequestHandler.class.getName(), |
||||
HandlerMethod.class.getName()); |
||||
} |
||||
|
||||
@Test |
||||
void methodName() { |
||||
assertThat(handlerMethod(new TestController(), "greet")).method().hasName("greet"); |
||||
} |
||||
|
||||
@Test |
||||
void declaringClass() { |
||||
assertThat(handlerMethod(new TestController(), "greet")).method().hasDeclaringClass(TestController.class); |
||||
} |
||||
|
||||
@Test |
||||
void method() { |
||||
assertThat(handlerMethod(new TestController(), "greet")).method().isEqualTo( |
||||
ReflectionUtils.findMethod(TestController.class, "greet")); |
||||
} |
||||
|
||||
@Test |
||||
void methodWithServletHandler() { |
||||
AssertProvider<HandlerResultAssert> actual = handler(new DefaultServletHttpRequestHandler()); |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(actual).method()) |
||||
.withMessageContainingAll(DefaultServletHttpRequestHandler.class.getName(), |
||||
HandlerMethod.class.getName()); |
||||
} |
||||
|
||||
@Test |
||||
void isInvokedOn() { |
||||
assertThat(handlerMethod(new TestController(), "greet")) |
||||
.isInvokedOn(TestController.class, TestController::greet); |
||||
} |
||||
|
||||
@Test |
||||
void isInvokedOnWithVoidMethod() { |
||||
assertThat(handlerMethod(new TestController(), "update")) |
||||
.isInvokedOn(TestController.class, controller -> { |
||||
controller.update(); |
||||
return controller; |
||||
}); |
||||
} |
||||
|
||||
@Test |
||||
void isInvokedOnWithWrongMethod() { |
||||
AssertProvider<HandlerResultAssert> actual = handlerMethod(new TestController(), "update"); |
||||
assertThatExceptionOfType(AssertionError.class) |
||||
.isThrownBy(() -> assertThat(actual).isInvokedOn(TestController.class, TestController::greet)) |
||||
.withMessageContainingAll( |
||||
method(TestController.class, "greet").toGenericString(), |
||||
method(TestController.class, "update").toGenericString()); |
||||
} |
||||
|
||||
|
||||
private static AssertProvider<HandlerResultAssert> handler(Object instance) { |
||||
return () -> new HandlerResultAssert(instance); |
||||
} |
||||
|
||||
private static AssertProvider<HandlerResultAssert> handlerMethod(Object instance, String name, Class<?>... parameterTypes) { |
||||
HandlerMethod handlerMethod = new HandlerMethod(instance, method(instance.getClass(), name, parameterTypes)); |
||||
return () -> new HandlerResultAssert(handlerMethod); |
||||
} |
||||
|
||||
private static Method method(Class<?> target, String name, Class<?>... parameterTypes) { |
||||
Method method = ReflectionUtils.findMethod(target, name, parameterTypes); |
||||
Assertions.assertThat(method).isNotNull(); |
||||
return method; |
||||
} |
||||
|
||||
@RestController |
||||
public static class TestController { |
||||
|
||||
@GetMapping("/greet") |
||||
public ResponseEntity<String> greet() { |
||||
return ResponseEntity.ok().body("Hello"); |
||||
} |
||||
|
||||
@PostMapping("/update") |
||||
public void update() { |
||||
} |
||||
|
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,176 @@
@@ -0,0 +1,176 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
|
||||
import org.assertj.core.api.AssertProvider; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.beans.MutablePropertyValues; |
||||
import org.springframework.beans.testfixture.beans.TestBean; |
||||
import org.springframework.validation.BindException; |
||||
import org.springframework.validation.DataBinder; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
|
||||
/** |
||||
* Tests for {@link ModelAssert}. |
||||
* |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class ModelAssertTests { |
||||
|
||||
@Test |
||||
void hasErrors() { |
||||
assertThat(forModel(new TestBean(), Map.of("name", "John", "age", "4x"))).hasErrors(); |
||||
} |
||||
|
||||
@Test |
||||
void hasErrorsWithNoError() { |
||||
AssertProvider<ModelAssert> actual = forModel(new TestBean(), Map.of("name", "John", "age", "42")); |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertThat(actual).hasErrors()) |
||||
.withMessageContainingAll("John", "to have at least one error"); |
||||
} |
||||
|
||||
@Test |
||||
void doesNotHaveErrors() { |
||||
assertThat(forModel(new TestBean(), Map.of("name", "John", "age", "42"))).doesNotHaveErrors(); |
||||
} |
||||
|
||||
@Test |
||||
void doesNotHaveErrorsWithError() { |
||||
AssertProvider<ModelAssert> actual = forModel(new TestBean(), Map.of("name", "John", "age", "4x")); |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> assertThat(actual).doesNotHaveErrors()) |
||||
.withMessageContainingAll("John", "to not have an error, but got 1"); |
||||
} |
||||
|
||||
@Test |
||||
void extractBindingResultForAttributeInError() { |
||||
Map<String, Object> model = new HashMap<>(); |
||||
augmentModel(model, "person", new TestBean(), Map.of("name", "John", "age", "4x", "touchy", "invalid.value")); |
||||
assertThat(forModel(model)).extractingBindingResult("person").hasErrorsCount(2); |
||||
} |
||||
|
||||
@Test |
||||
void hasErrorCountForUnknownAttribute() { |
||||
Map<String, Object> model = new HashMap<>(); |
||||
augmentModel(model, "person", new TestBean(), Map.of("name", "John", "age", "42")); |
||||
AssertProvider<ModelAssert> actual = forModel(model); |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||
() -> assertThat(actual).extractingBindingResult("user")) |
||||
.withMessageContainingAll("to have a binding result for attribute 'user'"); |
||||
} |
||||
|
||||
@Test |
||||
void hasErrorsWithMatchingAttributes() { |
||||
Map<String, Object> model = new HashMap<>(); |
||||
augmentModel(model, "wrong1", new TestBean(), Map.of("name", "first", "age", "4x")); |
||||
augmentModel(model, "valid", new TestBean(), Map.of("name", "second")); |
||||
augmentModel(model, "wrong2", new TestBean(), Map.of("name", "third", "touchy", "invalid.name")); |
||||
assertThat(forModel(model)).hasAttributeErrors("wrong1", "wrong2"); |
||||
} |
||||
|
||||
@Test |
||||
void hasErrorsWithOneNonMatchingAttribute() { |
||||
Map<String, Object> model = new HashMap<>(); |
||||
augmentModel(model, "wrong1", new TestBean(), Map.of("name", "first", "age", "4x")); |
||||
augmentModel(model, "valid", new TestBean(), Map.of("name", "second")); |
||||
augmentModel(model, "wrong2", new TestBean(), Map.of("name", "third", "touchy", "invalid.name")); |
||||
AssertProvider<ModelAssert> actual = forModel(model); |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||
() -> assertThat(actual).hasAttributeErrors("wrong1", "valid")) |
||||
.withMessageContainingAll("to have attribute errors for:", "wrong1, valid", |
||||
"but these attributes do not have any error:", "valid"); |
||||
} |
||||
|
||||
@Test |
||||
void hasErrorsWithOneNonMatchingAttributeAndOneUnknownAttribute() { |
||||
Map<String, Object> model = new HashMap<>(); |
||||
augmentModel(model, "wrong1", new TestBean(), Map.of("name", "first", "age", "4x")); |
||||
augmentModel(model, "valid", new TestBean(), Map.of("name", "second")); |
||||
augmentModel(model, "wrong2", new TestBean(), Map.of("name", "third", "touchy", "invalid.name")); |
||||
AssertProvider<ModelAssert> actual = forModel(model); |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||
() -> assertThat(actual).hasAttributeErrors("wrong1", "unknown", "valid")) |
||||
.withMessageContainingAll("to have attribute errors for:", "wrong1, unknown, valid", |
||||
"but could not find these attributes:", "unknown", |
||||
"and these attributes do not have any error:", "valid"); |
||||
} |
||||
|
||||
@Test |
||||
void doesNotHaveErrorsWithMatchingAttributes() { |
||||
Map<String, Object> model = new HashMap<>(); |
||||
augmentModel(model, "valid1", new TestBean(), Map.of("name", "first")); |
||||
augmentModel(model, "wrong", new TestBean(), Map.of("name", "second", "age", "4x")); |
||||
augmentModel(model, "valid2", new TestBean(), Map.of("name", "third")); |
||||
assertThat(forModel(model)).doesNotHaveAttributeErrors("valid1", "valid2"); |
||||
} |
||||
|
||||
@Test |
||||
void doesNotHaveErrorsWithOneNonMatchingAttribute() { |
||||
Map<String, Object> model = new HashMap<>(); |
||||
augmentModel(model, "valid1", new TestBean(), Map.of("name", "first")); |
||||
augmentModel(model, "wrong", new TestBean(), Map.of("name", "second", "age", "4x")); |
||||
augmentModel(model, "valid2", new TestBean(), Map.of("name", "third")); |
||||
AssertProvider<ModelAssert> actual = forModel(model); |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||
() -> assertThat(actual).doesNotHaveAttributeErrors("valid1", "wrong")) |
||||
.withMessageContainingAll("to have attribute without errors for:", "valid1, wrong", |
||||
"but these attributes have at least an error:", "wrong"); |
||||
} |
||||
|
||||
@Test |
||||
void doesNotHaveErrorsWithOneNonMatchingAttributeAndOneUnknownAttribute() { |
||||
Map<String, Object> model = new HashMap<>(); |
||||
augmentModel(model, "valid1", new TestBean(), Map.of("name", "first")); |
||||
augmentModel(model, "wrong", new TestBean(), Map.of("name", "second", "age", "4x")); |
||||
augmentModel(model, "valid2", new TestBean(), Map.of("name", "third")); |
||||
AssertProvider<ModelAssert> actual = forModel(model); |
||||
assertThatExceptionOfType(AssertionError.class).isThrownBy( |
||||
() -> assertThat(actual).doesNotHaveAttributeErrors("valid1", "unknown", "wrong")) |
||||
.withMessageContainingAll("to have attribute without errors for:", "valid1, unknown, wrong", |
||||
"but could not find these attributes:", "unknown", |
||||
"and these attributes have at least an error:", "wrong"); |
||||
} |
||||
|
||||
private AssertProvider<ModelAssert> forModel(Map<String, Object> model) { |
||||
return () -> new ModelAssert(model); |
||||
} |
||||
|
||||
private AssertProvider<ModelAssert> forModel(Object instance, Map<String, Object> propertyValues) { |
||||
Map<String, Object> model = new HashMap<>(); |
||||
augmentModel(model, "test", instance, propertyValues); |
||||
return forModel(model); |
||||
} |
||||
|
||||
private static void augmentModel(Map<String, Object> model, String attribute, Object instance, Map<String, Object> propertyValues) { |
||||
DataBinder binder = new DataBinder(instance, attribute); |
||||
MutablePropertyValues pvs = new MutablePropertyValues(propertyValues); |
||||
binder.bind(pvs); |
||||
try { |
||||
binder.close(); |
||||
model.putAll(binder.getBindingResult().getModel()); |
||||
} |
||||
catch (BindException ex) { |
||||
model.putAll(ex.getBindingResult().getModel()); |
||||
} |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,88 @@
@@ -0,0 +1,88 @@
|
||||
/* |
||||
* Copyright 2002-2024 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* https://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
package org.springframework.test.web.servlet.assertj; |
||||
|
||||
|
||||
import java.io.UnsupportedEncodingException; |
||||
import java.nio.charset.Charset; |
||||
import java.nio.charset.StandardCharsets; |
||||
|
||||
import org.assertj.core.api.AssertProvider; |
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.mock.web.MockHttpServletResponse; |
||||
import org.springframework.test.json.JsonContent; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat; |
||||
|
||||
/** |
||||
* Tests for {@link ResponseBodyAssert}. |
||||
* |
||||
* @author Brian Clozel |
||||
* @author Stephane Nicoll |
||||
*/ |
||||
class ResponseBodyAssertTests { |
||||
|
||||
@Test |
||||
void isEqualToWithByteArray() { |
||||
MockHttpServletResponse response = createResponse("hello"); |
||||
response.setCharacterEncoding(StandardCharsets.UTF_8.name()); |
||||
assertThat(fromResponse(response)).isEqualTo("hello".getBytes(StandardCharsets.UTF_8)); |
||||
} |
||||
|
||||
@Test |
||||
void isEqualToWithString() { |
||||
MockHttpServletResponse response = createResponse("hello"); |
||||
assertThat(fromResponse(response)).isEqualTo("hello"); |
||||
} |
||||
|
||||
@Test |
||||
void jsonPathWithJsonResponseShouldPass() { |
||||
MockHttpServletResponse response = createResponse("{\"message\": \"hello\"}"); |
||||
assertThat(fromResponse(response)).jsonPath().extractingPath("$.message").isEqualTo("hello"); |
||||
} |
||||
|
||||
@Test |
||||
void jsonPathWithJsonCompatibleResponseShouldPass() { |
||||
MockHttpServletResponse response = createResponse("{\"albumById\": {\"name\": \"Greatest hits\"}}"); |
||||
assertThat(fromResponse(response)).jsonPath() |
||||
.extractingPath("$.albumById.name").isEqualTo("Greatest hits"); |
||||
} |
||||
|
||||
@Test |
||||
void jsonCanLoadResourceRelativeToClass() { |
||||
MockHttpServletResponse response = createResponse("{ \"name\" : \"Spring\", \"age\" : 123 }"); |
||||
// See org/springframework/test/json/example.json
|
||||
assertThat(fromResponse(response)).json(JsonContent.class).isLenientlyEqualTo("example.json"); |
||||
} |
||||
|
||||
private MockHttpServletResponse createResponse(String body) { |
||||
try { |
||||
MockHttpServletResponse response = new MockHttpServletResponse(); |
||||
response.getWriter().print(body); |
||||
return response; |
||||
} |
||||
catch (UnsupportedEncodingException ex) { |
||||
throw new IllegalStateException(ex); |
||||
} |
||||
} |
||||
|
||||
private AssertProvider<ResponseBodyAssert> fromResponse(MockHttpServletResponse response) { |
||||
return () -> new ResponseBodyAssert(response.getContentAsByteArray(), Charset.forName(response.getCharacterEncoding()), null); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
{ |
||||
"gnirps": [ |
||||
"boot", |
||||
"framework" |
||||
] |
||||
} |
||||
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
{ |
||||
"name": "Spring", |
||||
"age": 123 |
||||
} |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
{ |
||||
"spring": [ |
||||
"framework", |
||||
"boot" |
||||
] |
||||
} |
||||
@ -0,0 +1,4 @@
@@ -0,0 +1,4 @@
|
||||
{ |
||||
"valuename": "spring", |
||||
"nullname": null |
||||
} |
||||
@ -0,0 +1,36 @@
@@ -0,0 +1,36 @@
|
||||
{ |
||||
"familyMembers": [ |
||||
{ |
||||
"name": "Homer" |
||||
}, |
||||
{ |
||||
"name": "Marge" |
||||
}, |
||||
{ |
||||
"name": "Bart" |
||||
}, |
||||
{ |
||||
"name": "Lisa" |
||||
}, |
||||
{ |
||||
"name": "Maggie" |
||||
} |
||||
], |
||||
"indexedFamilyMembers": { |
||||
"father": { |
||||
"name": "Homer" |
||||
}, |
||||
"mother": { |
||||
"name": "Marge" |
||||
}, |
||||
"son": { |
||||
"name": "Bart" |
||||
}, |
||||
"daughter": { |
||||
"name": "Lisa" |
||||
}, |
||||
"baby": { |
||||
"name": "Maggie" |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,6 @@
@@ -0,0 +1,6 @@
|
||||
{ |
||||
"spring": [ |
||||
"boot", |
||||
"framework" |
||||
] |
||||
} |
||||
@ -0,0 +1,18 @@
@@ -0,0 +1,18 @@
|
||||
{ |
||||
"str": "foo", |
||||
"num": 5, |
||||
"pi": 3.1415926, |
||||
"bool": true, |
||||
"arr": [ |
||||
42 |
||||
], |
||||
"colorMap": { |
||||
"red": "rojo" |
||||
}, |
||||
"whitespace": " ", |
||||
"emptyString": "", |
||||
"emptyArray": [ |
||||
], |
||||
"emptyMap": { |
||||
} |
||||
} |
||||
Loading…
Reference in new issue