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:
Stéphane Nicoll
2024-03-15 13:22:15 +01:00
parent 76f45c4289
commit e97ae434fb
18 changed files with 1863 additions and 0 deletions

View File

@@ -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;
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}

View File

@@ -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;

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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");
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}