Add AssertJ support for HTTP requests and responses
This commit adds AssertJ compatible assertions for HTTP request and responses, including headers, media type, and response body. The latter delegate to the recently included JSON assert support. See gh-21178
This commit is contained in:
@@ -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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/**
|
||||
* 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,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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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 @@
|
||||
/*
|
||||
* 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,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 @@
|
||||
/**
|
||||
* AssertJ support for MockMvc.
|
||||
*/
|
||||
@NonNullApi
|
||||
@NonNullFields
|
||||
package org.springframework.test.web.servlet.assertj;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
import org.springframework.lang.NonNullFields;
|
||||
Reference in New Issue
Block a user