mirror of
https://github.com/spring-projects/spring-framework
synced 2026-06-08 17:33:33 +00:00
AssertJ support for WebTestClient
Closes gh-35737
This commit is contained in:
+2
-1
@@ -111,9 +111,10 @@ class DefaultWebTestClient implements WebTestClient {
|
||||
Consumer<EntityExchangeResult<?>> entityResultConsumer,
|
||||
@Nullable Duration responseTimeout, DefaultWebTestClientBuilder clientBuilder) {
|
||||
|
||||
this.wiretapConnector = new WiretapConnector(connector);
|
||||
this.jsonEncoderDecoder = JsonEncoderDecoder.from(
|
||||
exchangeStrategies.messageWriters(), exchangeStrategies.messageReaders());
|
||||
|
||||
this.wiretapConnector = new WiretapConnector(connector, this.jsonEncoderDecoder);
|
||||
this.exchangeFunction = exchangeFactory.apply(this.wiretapConnector);
|
||||
this.uriBuilderFactory = uriBuilderFactory;
|
||||
this.defaultHeaders = headers;
|
||||
|
||||
+16
-1
@@ -36,6 +36,7 @@ import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.test.json.JsonConverterDelegate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
|
||||
@@ -78,6 +79,8 @@ public class ExchangeResult {
|
||||
|
||||
private final @Nullable Object mockServerResult;
|
||||
|
||||
private final @Nullable JsonConverterDelegate converterDelegate;
|
||||
|
||||
/** Ensure single logging, for example, for expectAll. */
|
||||
private boolean diagnosticsLogged;
|
||||
|
||||
@@ -93,10 +96,11 @@ public class ExchangeResult {
|
||||
* @param timeout how long to wait for content to materialize
|
||||
* @param uriTemplate the URI template used to set up the request, if any
|
||||
* @param serverResult the result of a mock server exchange if applicable.
|
||||
* @param converterDelegate for JSON decoding in AssertJ support
|
||||
*/
|
||||
ExchangeResult(ClientHttpRequest request, ClientHttpResponse response,
|
||||
Mono<byte[]> requestBody, Mono<byte[]> responseBody, Duration timeout, @Nullable String uriTemplate,
|
||||
@Nullable Object serverResult) {
|
||||
@Nullable Object serverResult, @Nullable JsonConverterDelegate converterDelegate) {
|
||||
|
||||
Assert.notNull(request, "ClientHttpRequest is required");
|
||||
Assert.notNull(response, "ClientHttpResponse is required");
|
||||
@@ -110,6 +114,7 @@ public class ExchangeResult {
|
||||
this.timeout = timeout;
|
||||
this.uriTemplate = uriTemplate;
|
||||
this.mockServerResult = serverResult;
|
||||
this.converterDelegate = converterDelegate;
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -123,6 +128,7 @@ public class ExchangeResult {
|
||||
this.timeout = other.timeout;
|
||||
this.uriTemplate = other.uriTemplate;
|
||||
this.mockServerResult = other.mockServerResult;
|
||||
this.converterDelegate = other.converterDelegate;
|
||||
this.diagnosticsLogged = other.diagnosticsLogged;
|
||||
}
|
||||
|
||||
@@ -206,6 +212,15 @@ public class ExchangeResult {
|
||||
return this.mockServerResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link JsonConverterDelegate} based on the configured codecs.
|
||||
* Mainly for internal use from AssertJ support classes.
|
||||
* @since 7.0
|
||||
*/
|
||||
public @Nullable JsonConverterDelegate getJsonConverterDelegate() {
|
||||
return this.converterDelegate;
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute the given Runnable, catch any {@link AssertionError}, log details
|
||||
* about the request and response at ERROR level under the class log
|
||||
|
||||
+45
@@ -16,6 +16,7 @@
|
||||
|
||||
package org.springframework.test.web.reactive.server;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Stream;
|
||||
@@ -25,11 +26,17 @@ import org.jspecify.annotations.Nullable;
|
||||
import org.springframework.core.ResolvableType;
|
||||
import org.springframework.core.codec.Decoder;
|
||||
import org.springframework.core.codec.Encoder;
|
||||
import org.springframework.core.io.buffer.DataBuffer;
|
||||
import org.springframework.core.io.buffer.DataBufferUtils;
|
||||
import org.springframework.core.io.buffer.DefaultDataBufferFactory;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.codec.DecoderHttpMessageReader;
|
||||
import org.springframework.http.codec.EncoderHttpMessageWriter;
|
||||
import org.springframework.http.codec.HttpMessageReader;
|
||||
import org.springframework.http.codec.HttpMessageWriter;
|
||||
import org.springframework.test.json.JsonConverterDelegate;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.MimeTypeUtils;
|
||||
|
||||
/**
|
||||
* {@link Encoder} and {@link Decoder} that is able to encode and decode
|
||||
@@ -49,6 +56,14 @@ record JsonEncoderDecoder(Encoder<?> encoder, Decoder<?> decoder) {
|
||||
private static final ResolvableType MAP_TYPE = ResolvableType.forClass(Map.class);
|
||||
|
||||
|
||||
/**
|
||||
* Return a {@link JsonConverterDelegate} that uses the encoder and decoder.
|
||||
*/
|
||||
public JsonConverterDelegate createJsonConverterDelegate() {
|
||||
return new CodecsJsonConverterDelegate();
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Create a {@link JsonEncoderDecoder} instance based on the specified
|
||||
* infrastructure.
|
||||
@@ -107,4 +122,34 @@ record JsonEncoderDecoder(Encoder<?> encoder, Decoder<?> decoder) {
|
||||
.orElse(null);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Implementation that delegates to the contained Encoder and Decoder.
|
||||
*/
|
||||
private class CodecsJsonConverterDelegate implements JsonConverterDelegate {
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T read(String content, ResolvableType targetType) {
|
||||
byte[] bytes = content.getBytes(StandardCharsets.UTF_8);
|
||||
DataBuffer buffer = DefaultDataBufferFactory.sharedInstance.wrap(bytes);
|
||||
Object value = decoder().decode(buffer, targetType, MediaType.APPLICATION_JSON, null);
|
||||
Assert.state(value != null, () -> "Could not decode JSON content: " + content);
|
||||
return (T) value;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
@Override
|
||||
public <T> T map(Object value, ResolvableType targetType) {
|
||||
DataBuffer buffer = ((Encoder<T>) encoder()).encodeValue((T) value,
|
||||
DefaultDataBufferFactory.sharedInstance, targetType, MimeTypeUtils.APPLICATION_JSON, null);
|
||||
try {
|
||||
return read(buffer.toString(StandardCharsets.UTF_8), targetType);
|
||||
}
|
||||
finally {
|
||||
DataBufferUtils.release(buffer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
+7
-2
@@ -38,6 +38,7 @@ import org.springframework.http.client.reactive.ClientHttpRequest;
|
||||
import org.springframework.http.client.reactive.ClientHttpRequestDecorator;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponse;
|
||||
import org.springframework.http.client.reactive.ClientHttpResponseDecorator;
|
||||
import org.springframework.test.json.JsonConverterDelegate;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
@@ -53,11 +54,14 @@ class WiretapConnector implements ClientHttpConnector {
|
||||
|
||||
private final ClientHttpConnector delegate;
|
||||
|
||||
private final @Nullable JsonConverterDelegate converterDelegate;
|
||||
|
||||
private final Map<String, ClientExchangeInfo> exchanges = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
WiretapConnector(ClientHttpConnector delegate) {
|
||||
WiretapConnector(ClientHttpConnector delegate, @Nullable JsonEncoderDecoder encoderDecoder) {
|
||||
this.delegate = delegate;
|
||||
this.converterDelegate = (encoderDecoder != null ? encoderDecoder.createJsonConverterDelegate() : null);
|
||||
}
|
||||
|
||||
|
||||
@@ -96,7 +100,8 @@ class WiretapConnector implements ClientHttpConnector {
|
||||
clientInfo.getRequest().getRecorder().getContent(),
|
||||
clientInfo.getResponse().getRecorder().getContent(),
|
||||
timeout, uriTemplate,
|
||||
clientInfo.getResponse().getMockServerResult());
|
||||
clientInfo.getResponse().getMockServerResult(),
|
||||
this.converterDelegate);
|
||||
}
|
||||
|
||||
|
||||
|
||||
+51
@@ -0,0 +1,51 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.reactive.server.assertj;
|
||||
|
||||
|
||||
import org.springframework.test.web.reactive.server.ExchangeResult;
|
||||
|
||||
/**
|
||||
* Default implementation of {@link WebTestClientResponse}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 7.0
|
||||
*/
|
||||
final class DefaultWebTestClientResponse implements WebTestClientResponse {
|
||||
|
||||
private final ExchangeResult exchangeResult;
|
||||
|
||||
|
||||
DefaultWebTestClientResponse(ExchangeResult exchangeResult) {
|
||||
this.exchangeResult = exchangeResult;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public ExchangeResult getExchangeResult() {
|
||||
return this.exchangeResult;
|
||||
}
|
||||
|
||||
/**
|
||||
* Use AssertJ's {@link org.assertj.core.api.Assertions#assertThat assertThat} instead.
|
||||
*/
|
||||
@Override
|
||||
public WebTestClientResponseAssert assertThat() {
|
||||
return new WebTestClientResponseAssert(this);
|
||||
}
|
||||
|
||||
}
|
||||
+163
@@ -0,0 +1,163 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.reactive.server.assertj;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
import org.assertj.core.api.AbstractMapAssert;
|
||||
import org.assertj.core.api.Assertions;
|
||||
|
||||
import org.springframework.http.ResponseCookie;
|
||||
|
||||
/**
|
||||
* AssertJ {@linkplain org.assertj.core.api.Assert assertions} that can be applied
|
||||
* to {@link ResponseCookie cookies}.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 7.0
|
||||
*/
|
||||
public class ResponseCookieMapAssert
|
||||
extends AbstractMapAssert<ResponseCookieMapAssert, Map<String, ResponseCookie>, String, ResponseCookie> {
|
||||
|
||||
|
||||
public ResponseCookieMapAssert(ResponseCookie[] actual) {
|
||||
super(toMap(actual), ResponseCookieMapAssert.class);
|
||||
as("Cookies");
|
||||
}
|
||||
|
||||
private static Map<String, ResponseCookie> toMap(ResponseCookie[] cookies) {
|
||||
Map<String, ResponseCookie> map = new LinkedHashMap<>();
|
||||
for (ResponseCookie cookie : cookies) {
|
||||
map.putIfAbsent(cookie.getName(), cookie);
|
||||
}
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify that the actual cookies contain a cookie with the given {@code name}.
|
||||
* @param name the name of an expected cookie
|
||||
* @see #containsKey
|
||||
*/
|
||||
public ResponseCookieMapAssert containsCookie(String name) {
|
||||
return containsKey(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual cookies contain cookies with the given {@code names}.
|
||||
* @param names the names of expected cookies
|
||||
* @see #containsKeys
|
||||
*/
|
||||
public ResponseCookieMapAssert containsCookies(String... names) {
|
||||
return containsKeys(names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual cookies do not contain a cookie with the given
|
||||
* {@code name}.
|
||||
* @param name the name of a cookie that should not be present
|
||||
* @see #doesNotContainKey
|
||||
*/
|
||||
public ResponseCookieMapAssert doesNotContainCookie(String name) {
|
||||
return doesNotContainKey(name);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual cookies do not contain any cookies with the given
|
||||
* {@code names}.
|
||||
* @param names the names of cookies that should not be present
|
||||
* @see #doesNotContainKeys
|
||||
*/
|
||||
public ResponseCookieMapAssert doesNotContainCookies(String... names) {
|
||||
return doesNotContainKeys(names);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual cookies contain a cookie with the given {@code name}
|
||||
* that satisfies the given {@code cookieRequirements}.
|
||||
* @param name the name of an expected cookie
|
||||
* @param cookieRequirements the requirements for the cookie
|
||||
*/
|
||||
public ResponseCookieMapAssert hasCookieSatisfying(String name, Consumer<ResponseCookie> cookieRequirements) {
|
||||
return hasEntrySatisfying(name, cookieRequirements);
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual cookies contain a cookie with the given {@code name}
|
||||
* whose {@linkplain ResponseCookie#getValue() value} is equal to the given one.
|
||||
* @param name the name of the cookie
|
||||
* @param expected the expected value of the cookie
|
||||
*/
|
||||
public ResponseCookieMapAssert hasValue(String name, String expected) {
|
||||
return hasCookieSatisfying(name, cookie -> Assertions.assertThat(cookie.getValue()).isEqualTo(expected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual cookies contain a cookie with the given {@code name}
|
||||
* whose {@linkplain ResponseCookie#getMaxAge() max age} is equal to the given one.
|
||||
* @param name the name of the cookie
|
||||
* @param expected the expected max age of the cookie
|
||||
*/
|
||||
public ResponseCookieMapAssert hasMaxAge(String name, Duration expected) {
|
||||
return hasCookieSatisfying(name, cookie -> Assertions.assertThat(cookie.getMaxAge()).isEqualTo(expected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual cookies contain a cookie with the given {@code name}
|
||||
* whose {@linkplain ResponseCookie#getPath() path} is equal to the given one.
|
||||
* @param name the name of the cookie
|
||||
* @param expected the expected path of the cookie
|
||||
*/
|
||||
public ResponseCookieMapAssert hasPath(String name, String expected) {
|
||||
return hasCookieSatisfying(name, cookie -> Assertions.assertThat(cookie.getPath()).isEqualTo(expected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual cookies contain a cookie with the given {@code name}
|
||||
* whose {@linkplain ResponseCookie#getDomain() domain} is equal to the given one.
|
||||
* @param name the name of the cookie
|
||||
* @param expected the expected domain of the cookie
|
||||
*/
|
||||
public ResponseCookieMapAssert hasDomain(String name, String expected) {
|
||||
return hasCookieSatisfying(name, cookie -> Assertions.assertThat(cookie.getDomain()).isEqualTo(expected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual cookies contain a cookie with the given {@code name}
|
||||
* whose {@linkplain ResponseCookie#isSecure() secure flag} is equal to the give one.
|
||||
* @param name the name of the cookie
|
||||
* @param expected whether the cookie is secure
|
||||
*/
|
||||
public ResponseCookieMapAssert isSecure(String name, boolean expected) {
|
||||
return hasCookieSatisfying(name, cookie -> Assertions.assertThat(cookie.isSecure()).isEqualTo(expected));
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the actual cookies contain a cookie with the given {@code name}
|
||||
* whose {@linkplain ResponseCookie#isHttpOnly() http only flag} is equal to the given
|
||||
* one.
|
||||
* @param name the name of the cookie
|
||||
* @param expected whether the cookie is http only
|
||||
*/
|
||||
public ResponseCookieMapAssert isHttpOnly(String name, boolean expected) {
|
||||
return hasCookieSatisfying(name, cookie -> Assertions.assertThat(cookie.isHttpOnly()).isEqualTo(expected));
|
||||
}
|
||||
|
||||
}
|
||||
+64
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.reactive.server.assertj;
|
||||
|
||||
import org.assertj.core.api.AssertProvider;
|
||||
|
||||
import org.springframework.test.web.reactive.server.ExchangeResult;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
|
||||
|
||||
/**
|
||||
* {@link AssertProvider} for {@link WebTestClientResponseAssert} that holds the
|
||||
* result of an exchange performed through {@link WebTestClient}. Intended for
|
||||
* further use with AssertJ. For example:
|
||||
*
|
||||
* <pre class="code">
|
||||
* ResponseSpec spec = webTestClient.get().uri("/greeting").exchange();
|
||||
*
|
||||
* WebTestClientResponse response = WebTestClientResponse.from(spec);
|
||||
* assertThat(response).hasStatusOk();
|
||||
* assertThat(response).contentType().isCompatibleWith(MediaType.APPLICATION_JSON);
|
||||
* assertThat(response).bodyJson().extractingPath("$.message").asString().isEqualTo("Hello World");
|
||||
* </pre>
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 7.0
|
||||
*/
|
||||
public interface WebTestClientResponse extends AssertProvider<WebTestClientResponseAssert> {
|
||||
|
||||
/**
|
||||
* Return the underlying {@link ExchangeResult}.
|
||||
*/
|
||||
ExchangeResult getExchangeResult();
|
||||
|
||||
|
||||
/**
|
||||
* Create an instance from a {@link WebTestClient.ResponseSpec}.
|
||||
*/
|
||||
static WebTestClientResponse from(WebTestClient.ResponseSpec spec) {
|
||||
return from(spec.returnResult(byte[].class));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an instance from an {@link ExchangeResult}.
|
||||
*/
|
||||
static WebTestClientResponse from(ExchangeResult result) {
|
||||
return new DefaultWebTestClientResponse(result);
|
||||
}
|
||||
|
||||
}
|
||||
+337
@@ -0,0 +1,337 @@
|
||||
/*
|
||||
* Copyright 2002-present 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.reactive.server.assertj;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Collection;
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.assertj.core.api.AbstractByteArrayAssert;
|
||||
import org.assertj.core.api.AbstractIntegerAssert;
|
||||
import org.assertj.core.api.AbstractObjectAssert;
|
||||
import org.assertj.core.api.AbstractStringAssert;
|
||||
import org.assertj.core.api.Assertions;
|
||||
import org.assertj.core.api.ByteArrayAssert;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.HttpStatus;
|
||||
import org.springframework.http.HttpStatusCode;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.http.ResponseCookie;
|
||||
import org.springframework.test.http.HttpHeadersAssert;
|
||||
import org.springframework.test.http.MediaTypeAssert;
|
||||
import org.springframework.test.json.AbstractJsonContentAssert;
|
||||
import org.springframework.test.json.JsonContent;
|
||||
import org.springframework.test.json.JsonContentAssert;
|
||||
import org.springframework.test.web.reactive.server.ExchangeResult;
|
||||
import org.springframework.util.function.SingletonSupplier;
|
||||
|
||||
/**
|
||||
* AssertJ {@linkplain org.assertj.core.api.Assert assertions} for the result
|
||||
* from a {@link org.springframework.test.web.reactive.server.WebTestClient}
|
||||
* exchange.
|
||||
*
|
||||
* @author Rossen Stoyanchev
|
||||
* @since 7.0
|
||||
*/
|
||||
@SuppressWarnings({"UnusedReturnValue", "unused"})
|
||||
public class WebTestClientResponseAssert
|
||||
extends AbstractObjectAssert<WebTestClientResponseAssert, WebTestClientResponse> {
|
||||
|
||||
private final Supplier<MediaTypeAssert> contentTypeAssertSupplier;
|
||||
|
||||
private final Supplier<HttpHeadersAssert> headersAssertSupplier;
|
||||
|
||||
private final Supplier<AbstractIntegerAssert<?>> statusAssert;
|
||||
|
||||
|
||||
WebTestClientResponseAssert(WebTestClientResponse actual) {
|
||||
super(actual, WebTestClientResponseAssert.class);
|
||||
|
||||
this.contentTypeAssertSupplier = SingletonSupplier.of(() ->
|
||||
new MediaTypeAssert(getExchangeResult().getResponseHeaders().getContentType()));
|
||||
|
||||
this.headersAssertSupplier = SingletonSupplier.of(() ->
|
||||
new HttpHeadersAssert(getExchangeResult().getResponseHeaders()));
|
||||
|
||||
this.statusAssert = SingletonSupplier.of(() ->
|
||||
Assertions.assertThat(getExchangeResult().getStatus().value()).as("HTTP status code"));
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Verify that the HTTP status is equal to the specified status code.
|
||||
* @param status the expected HTTP status code
|
||||
*/
|
||||
public WebTestClientResponseAssert 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 WebTestClientResponseAssert hasStatus(HttpStatus status) {
|
||||
return hasStatus(status.value());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the HTTP status is equal to {@link HttpStatus#OK}.
|
||||
* @see #hasStatus(HttpStatus)
|
||||
*/
|
||||
public WebTestClientResponseAssert 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 WebTestClientResponseAssert hasStatus1xxInformational() {
|
||||
return hasStatusSeries(HttpStatus.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 WebTestClientResponseAssert hasStatus2xxSuccessful() {
|
||||
return hasStatusSeries(HttpStatus.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 WebTestClientResponseAssert hasStatus3xxRedirection() {
|
||||
return hasStatusSeries(HttpStatus.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 WebTestClientResponseAssert hasStatus4xxClientError() {
|
||||
return hasStatusSeries(HttpStatus.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 WebTestClientResponseAssert hasStatus5xxServerError() {
|
||||
return hasStatusSeries(HttpStatus.Series.SERVER_ERROR);
|
||||
}
|
||||
|
||||
private WebTestClientResponseAssert hasStatusSeries(HttpStatus.Series series) {
|
||||
HttpStatusCode status = getExchangeResult().getStatus();
|
||||
Assertions.assertThat(HttpStatus.Series.resolve(status.value())).as("HTTP status series").isEqualTo(series);
|
||||
return this.myself;
|
||||
}
|
||||
|
||||
private AbstractIntegerAssert<?> status() {
|
||||
return this.statusAssert.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@linkplain HttpHeadersAssert assertion} object that uses
|
||||
* {@link HttpHeaders} as the object to test. The returned assertion object
|
||||
* provides all the regular {@linkplain org.assertj.core.api.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 response contains a header with the given {@code name}.
|
||||
* @param name the name of an expected HTTP header
|
||||
*/
|
||||
public WebTestClientResponseAssert containsHeader(String name) {
|
||||
headers().containsHeader(name);
|
||||
return this.myself;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the response does not contain a header with the given {@code name}.
|
||||
* @param name the name of an HTTP header that should not be present
|
||||
*/
|
||||
public WebTestClientResponseAssert doesNotContainHeader(String name) {
|
||||
headers().doesNotContainHeader(name);
|
||||
return this.myself;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the response contains a header with the given {@code name}
|
||||
* and primary {@code value}.
|
||||
* @param name the name of an expected HTTP header
|
||||
* @param value the expected value of the header
|
||||
*/
|
||||
public WebTestClientResponseAssert hasHeader(String name, String value) {
|
||||
headers().hasValue(name, value);
|
||||
return this.myself;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@linkplain MediaTypeAssert assertion} object that uses the
|
||||
* response's {@linkplain MediaType content type} as the object to test.
|
||||
*/
|
||||
public MediaTypeAssert contentType() {
|
||||
return this.contentTypeAssertSupplier.get();
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the response's {@code Content-Type} is equal to the given value.
|
||||
* @param contentType the expected content type
|
||||
*/
|
||||
public WebTestClientResponseAssert hasContentType(MediaType contentType) {
|
||||
contentType().isEqualTo(contentType);
|
||||
return this.myself;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the response's {@code Content-Type} is equal to the given
|
||||
* string representation.
|
||||
* @param contentType the expected content type
|
||||
*/
|
||||
public WebTestClientResponseAssert hasContentType(String contentType) {
|
||||
contentType().isEqualTo(contentType);
|
||||
return this.myself;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the response's {@code Content-Type} is
|
||||
* {@linkplain MediaType#isCompatibleWith(MediaType) compatible} with the
|
||||
* given value.
|
||||
* @param contentType the expected compatible content type
|
||||
*/
|
||||
public WebTestClientResponseAssert hasContentTypeCompatibleWith(MediaType contentType) {
|
||||
contentType().isCompatibleWith(contentType);
|
||||
return this.myself;
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the response's {@code Content-Type} is
|
||||
* {@linkplain MediaType#isCompatibleWith(MediaType) compatible} with the
|
||||
* given string representation.
|
||||
* @param contentType the expected compatible content type
|
||||
*/
|
||||
public WebTestClientResponseAssert hasContentTypeCompatibleWith(String contentType) {
|
||||
contentType().isCompatibleWith(contentType);
|
||||
return this.myself;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@linkplain ResponseCookieMapAssert assertion} object that uses the
|
||||
* response's {@linkplain ResponseCookie cookies} as the object to test.
|
||||
*/
|
||||
public ResponseCookieMapAssert cookies() {
|
||||
return new ResponseCookieMapAssert(getCookies());
|
||||
}
|
||||
|
||||
private ResponseCookie[] getCookies() {
|
||||
return getExchangeResult().getResponseCookies().values().stream()
|
||||
.flatMap(Collection::stream)
|
||||
.toArray(ResponseCookie[]:: new);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@linkplain AbstractByteArrayAssert assertion} object that
|
||||
* uses the response body as the object to test.
|
||||
* @see #bodyText()
|
||||
* @see #bodyJson()
|
||||
*/
|
||||
public AbstractByteArrayAssert<?> body() {
|
||||
return new ByteArrayAssert(getExchangeResult().getResponseBodyContent());
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@linkplain AbstractStringAssert assertion} object that uses
|
||||
* the response body converted to text as the object to test.
|
||||
* <p>Examples: <pre><code class="java">
|
||||
* // Check that the response body is equal to "Hello World":
|
||||
* assertThat(response).bodyText().isEqualTo("Hello World");
|
||||
* </code></pre>
|
||||
*/
|
||||
public AbstractStringAssert<?> bodyText() {
|
||||
return Assertions.assertThat(readBody());
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify that the response body is equal to the given value.
|
||||
*/
|
||||
public WebTestClientResponseAssert hasBodyTextEqualTo(String bodyText) {
|
||||
bodyText().isEqualTo(bodyText);
|
||||
return this.myself;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a new {@linkplain AbstractJsonContentAssert assertion} object that
|
||||
* uses the response body converted to text as the object to test. Compared
|
||||
* to {@link #bodyText()}, the assertion object provides dedicated JSON
|
||||
* support.
|
||||
* <p>Examples: <pre><code class="java">
|
||||
* // Check that the response body is strictly equal to the content of
|
||||
* // "/com/acme/sample/person-created.json":
|
||||
* assertThat(response).bodyJson()
|
||||
* .isStrictlyEqualToJson("/com/acme/sample/person-created.json");
|
||||
*
|
||||
* // Check that the response is strictly equal to the content of the
|
||||
* // specified file located in the same package as the PersonController:
|
||||
* assertThat(response).bodyJson().withResourceLoadClass(PersonController.class)
|
||||
* .isStrictlyEqualToJson("person-created.json");
|
||||
* </code></pre>
|
||||
* The returned assert object also supports JSON path expressions.
|
||||
* <p>Examples: <pre><code class="java">
|
||||
* // Check that the JSON document does not have an "error" element
|
||||
* assertThat(response).bodyJson().doesNotHavePath("$.error");
|
||||
*
|
||||
* // Check that the JSON document as a top level "message" element
|
||||
* assertThat(response).bodyJson()
|
||||
* .extractingPath("$.message").asString().isEqualTo("hello");
|
||||
* </code></pre>
|
||||
*/
|
||||
public AbstractJsonContentAssert<?> bodyJson() {
|
||||
return new JsonContentAssert(new JsonContent(readBody(), getExchangeResult().getJsonConverterDelegate()));
|
||||
}
|
||||
|
||||
private String readBody() {
|
||||
return new String(getExchangeResult().getResponseBodyContent(), getCharset());
|
||||
}
|
||||
|
||||
private Charset getCharset() {
|
||||
ExchangeResult result = getExchangeResult();
|
||||
MediaType contentType = result.getResponseHeaders().getContentType();
|
||||
Charset charset = (contentType != null ? contentType.getCharset() : null);
|
||||
return (charset != null ? charset : StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private ExchangeResult getExchangeResult() {
|
||||
return this.actual.getExchangeResult();
|
||||
}
|
||||
|
||||
}
|
||||
+7
@@ -0,0 +1,7 @@
|
||||
/**
|
||||
* AssertJ support for WebTestClient.
|
||||
*/
|
||||
@NullMarked
|
||||
package org.springframework.test.web.reactive.server.assertj;
|
||||
|
||||
import org.jspecify.annotations.NullMarked;
|
||||
Reference in New Issue
Block a user