AssertJ support for WebTestClient

Closes gh-35737
This commit is contained in:
rstoyanchev
2025-11-04 16:33:50 +00:00
parent cd8690254b
commit 7445f542f4
12 changed files with 995 additions and 5 deletions
@@ -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;
@@ -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
@@ -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);
}
}
}
}
@@ -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);
}
@@ -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);
}
}
@@ -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));
}
}
@@ -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);
}
}
@@ -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();
}
}
@@ -0,0 +1,7 @@
/**
* AssertJ support for WebTestClient.
*/
@NullMarked
package org.springframework.test.web.reactive.server.assertj;
import org.jspecify.annotations.NullMarked;