diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java index 327ad3de13e..e95fb538f35 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/DefaultRestTestClient.java @@ -38,6 +38,8 @@ import org.springframework.http.MediaType; import org.springframework.http.client.ClientHttpRequestExecution; import org.springframework.http.client.ClientHttpRequestInterceptor; import org.springframework.http.client.ClientHttpResponse; +import org.springframework.http.converter.HttpMessageConverters; +import org.springframework.test.http.HttpMessageContentConverter; import org.springframework.test.json.JsonAssert; import org.springframework.test.json.JsonComparator; import org.springframework.test.json.JsonCompareMode; @@ -67,6 +69,8 @@ class DefaultRestTestClient implements RestTestClient { private final DefaultRestTestClientBuilder restTestClientBuilder; + private final @Nullable HttpMessageContentConverter messageContentConverter; + private final AtomicLong requestIndex = new AtomicLong(); @@ -77,6 +81,7 @@ class DefaultRestTestClient implements RestTestClient { this.restClient = builder.requestInterceptor(this.wiretapInterceptor).build(); this.entityResultConsumer = entityResultConsumer; this.restTestClientBuilder = restTestClientBuilder; + this.messageContentConverter = new ConverterCallback(this.restClient).getConverter(); } @@ -131,6 +136,27 @@ class DefaultRestTestClient implements RestTestClient { } + private static class ConverterCallback { + + private @Nullable HttpMessageContentConverter converter; + + ConverterCallback(RestClient client) { + client.mutate() + .configureMessageConverters(convertersBuilder -> { + HttpMessageConverters converters = convertersBuilder.build(); + if (converters.iterator().hasNext()) { + this.converter = HttpMessageContentConverter.of(converters); + } + }) + .build(); + } + + public @Nullable HttpMessageContentConverter getConverter() { + return this.converter; + } + } + + private class DefaultRequestBodyUriSpec implements RequestBodyUriSpec { private final RestClient.RequestBodyUriSpec requestHeadersUriSpec; @@ -263,7 +289,8 @@ class DefaultRestTestClient implements RestTestClient { this.requestHeadersUriSpec.exchangeForRequiredValue( (request, response) -> { byte[] requestBody = wiretapInterceptor.getRequestContent(this.requestId); - return new ExchangeResult(request, response, this.uriTemplate, requestBody); + return new ExchangeResult( + request, response, this.uriTemplate, requestBody, messageContentConverter); }, false), DefaultRestTestClient.this.entityResultConsumer); } diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/ExchangeResult.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/ExchangeResult.java index 99cf979c0fa..c86b4d352ec 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/ExchangeResult.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/ExchangeResult.java @@ -38,6 +38,7 @@ 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.HttpMessageContentConverter; import org.springframework.util.Assert; import org.springframework.util.LinkedMultiValueMap; import org.springframework.util.MultiValueMap; @@ -74,13 +75,15 @@ public class ExchangeResult { private final byte[] requestBody; + private final @Nullable HttpMessageContentConverter messageContentConverter; + /** Ensure single logging; for example, for expectAll. */ private boolean diagnosticsLogged; ExchangeResult( HttpRequest request, ConvertibleClientHttpResponse response, @Nullable String uriTemplate, - byte[] requestBody) { + byte[] requestBody, @Nullable HttpMessageContentConverter messageContentConverter) { Assert.notNull(request, "HttpRequest must not be null"); Assert.notNull(response, "ClientHttpResponse must not be null"); @@ -88,10 +91,11 @@ public class ExchangeResult { this.clientResponse = response; this.uriTemplate = uriTemplate; this.requestBody = requestBody; + this.messageContentConverter = messageContentConverter; } ExchangeResult(ExchangeResult result) { - this(result.request, result.clientResponse, result.uriTemplate, result.requestBody); + this(result.request, result.clientResponse, result.uriTemplate, result.requestBody, result.messageContentConverter); this.diagnosticsLogged = result.diagnosticsLogged; } @@ -197,6 +201,14 @@ public class ExchangeResult { } } + /** + * Return a content converter that delegates to the configured HTTP message converters. + * Mainly for internal use from AssertJ support. + */ + public @Nullable HttpMessageContentConverter getMessageContentConverter() { + return this.messageContentConverter; + } + /** * Execute the given Runnable, catch any {@link AssertionError}, log details * about the request and response at ERROR level under the class log diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java index 9a9af7b797c..8c0e84a0150 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/RestTestClient.java @@ -647,13 +647,24 @@ public interface RestTestClient { BodyContentSpec expectBody(); /** - * Exit the chained flow in order to consume the response body externally. + * Return an {@link ExchangeResult} with the raw content. Effectively, a shortcut for: + *
+		 * .returnResult(byte[].class)
+		 * 
+ */ + default ExchangeResult returnResult() { + return returnResult(byte[].class); + } + + /** + * Convert the response content to the given target type, and return an + * {@link ExchangeResult} that represents the exchange. */ EntityExchangeResult returnResult(Class elementClass); /** - * Alternative to {@link #returnResult(Class)} that accepts information - * about a target type with generics. + * Alternative to {@link #returnResult(Class)} that allows specifying a + * response body type with generics. */ EntityExchangeResult returnResult(ParameterizedTypeReference elementTypeRef); diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/assertj/DefaultRestTestClientResponse.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/assertj/DefaultRestTestClientResponse.java new file mode 100644 index 00000000000..d15e354393a --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/assertj/DefaultRestTestClientResponse.java @@ -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.servlet.client.assertj; + +import org.springframework.test.web.servlet.client.ExchangeResult; + +/** + * Default implementation of {@link RestTestClientResponse}. + * + * @author Rossen Stoyanchev + * @since 7.0 + */ +final class DefaultRestTestClientResponse implements RestTestClientResponse { + + private final ExchangeResult exchangeResult; + + + DefaultRestTestClientResponse(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 RestTestClientResponseAssert assertThat() { + return new RestTestClientResponseAssert(this); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/assertj/RestTestClientResponse.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/assertj/RestTestClientResponse.java new file mode 100644 index 00000000000..cb713e1611d --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/assertj/RestTestClientResponse.java @@ -0,0 +1,63 @@ +/* + * 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.servlet.client.assertj; + +import org.assertj.core.api.AssertProvider; + +import org.springframework.test.web.servlet.client.ExchangeResult; +import org.springframework.test.web.servlet.client.RestTestClient; + +/** + * {@link AssertProvider} for {@link RestTestClientResponseAssert} that holds the + * result of an exchange performed through {@link RestTestClient}. Intended for + * further use with AssertJ. For example: + * + *
+ * ResponseSpec spec = restTestClient.get().uri("/greeting").exchange();
+ *
+ * RestTestClientResponse response = RestTestClientResponse.from(spec);
+ * assertThat(response).hasStatusOk();
+ * assertThat(response).contentType().isCompatibleWith(MediaType.APPLICATION_JSON);
+ * assertThat(response).bodyJson().extractingPath("$.message").asString().isEqualTo("Hello World");
+ * 
+ * + * @author Rossen Stoyanchev + * @since 7.0 + */ +public interface RestTestClientResponse extends AssertProvider { + + /** + * Return the underlying {@link ExchangeResult}. + */ + ExchangeResult getExchangeResult(); + + + /** + * Create an instance from a {@link RestTestClient.ResponseSpec}. + */ + static RestTestClientResponse from(RestTestClient.ResponseSpec spec) { + return from(spec.returnResult(byte[].class)); + } + + /** + * Create an instance from an {@link ExchangeResult}. + */ + static RestTestClientResponse from(ExchangeResult result) { + return new DefaultRestTestClientResponse(result); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/assertj/RestTestClientResponseAssert.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/assertj/RestTestClientResponseAssert.java new file mode 100644 index 00000000000..d69c49ecdc8 --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/assertj/RestTestClientResponseAssert.java @@ -0,0 +1,363 @@ +/* + * 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.servlet.client.assertj; + +import java.nio.charset.Charset; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; +import java.util.function.Supplier; + +import jakarta.servlet.http.Cookie; +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.servlet.assertj.CookieMapAssert; +import org.springframework.test.web.servlet.client.ExchangeResult; +import org.springframework.util.MultiValueMap; +import org.springframework.util.function.SingletonSupplier; + +/** + * AssertJ {@linkplain org.assertj.core.api.Assert assertions} for the result from a + * {@link org.springframework.test.web.servlet.client.RestTestClient} exchange. + * + * @author Rossen Stoyanchev + * @since 7.0 + */ +@SuppressWarnings({"UnusedReturnValue", "unused"}) +public class RestTestClientResponseAssert + extends AbstractObjectAssert { + + private final Supplier contentTypeAssertSupplier; + + private final Supplier headersAssertSupplier; + + private final Supplier> statusAssert; + + + RestTestClientResponseAssert(RestTestClientResponse actual) { + super(actual, RestTestClientResponseAssert.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 RestTestClientResponseAssert 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 RestTestClientResponseAssert hasStatus(HttpStatus status) { + return hasStatus(status.value()); + } + + /** + * Verify that the HTTP status is equal to {@link HttpStatus#OK}. + * @see #hasStatus(HttpStatus) + */ + public RestTestClientResponseAssert hasStatusOk() { + return hasStatus(HttpStatus.OK); + } + + /** + * Verify that the HTTP status code is in the 1xx range. + * @see RFC 2616 + */ + public RestTestClientResponseAssert hasStatus1xxInformational() { + return hasStatusSeries(HttpStatus.Series.INFORMATIONAL); + } + + /** + * Verify that the HTTP status code is in the 2xx range. + * @see RFC 2616 + */ + public RestTestClientResponseAssert hasStatus2xxSuccessful() { + return hasStatusSeries(HttpStatus.Series.SUCCESSFUL); + } + + /** + * Verify that the HTTP status code is in the 3xx range. + * @see RFC 2616 + */ + public RestTestClientResponseAssert hasStatus3xxRedirection() { + return hasStatusSeries(HttpStatus.Series.REDIRECTION); + } + + /** + * Verify that the HTTP status code is in the 4xx range. + * @see RFC 2616 + */ + public RestTestClientResponseAssert hasStatus4xxClientError() { + return hasStatusSeries(HttpStatus.Series.CLIENT_ERROR); + } + + /** + * Verify that the HTTP status code is in the 5xx range. + * @see RFC 2616 + */ + public RestTestClientResponseAssert hasStatus5xxServerError() { + return hasStatusSeries(HttpStatus.Series.SERVER_ERROR); + } + + private RestTestClientResponseAssert 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:

+	 * // 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);
+	 * 
+ */ + 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 RestTestClientResponseAssert 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 RestTestClientResponseAssert 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 RestTestClientResponseAssert 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 RestTestClientResponseAssert 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 RestTestClientResponseAssert 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 RestTestClientResponseAssert 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 RestTestClientResponseAssert hasContentTypeCompatibleWith(String contentType) { + contentType().isCompatibleWith(contentType); + return this.myself; + } + + /** + * Return a new {@linkplain CookieMapAssert assertion} object that uses the + * response's {@linkplain Cookie cookies} as the object to test. + */ + public CookieMapAssert cookies() { + return new CookieMapAssert(getCookies()); + } + + private Cookie[] getCookies() { + List cookies = new ArrayList<>(); + MultiValueMap responseCookies = getExchangeResult().getResponseCookies(); + for (String name : responseCookies.keySet()) { + for (ResponseCookie responseCookie : responseCookies.get(name)) { + Cookie cookie = new Cookie(name, responseCookie.getValue()); + if (!responseCookie.getMaxAge().isNegative()) { + cookie.setMaxAge((int) responseCookie.getMaxAge().getSeconds()); + } + if (responseCookie.getDomain() != null) { + cookie.setDomain(responseCookie.getDomain()); + } + if (responseCookie.getPath() != null) { + cookie.setPath(responseCookie.getPath()); + } + if (responseCookie.getSameSite() != null) { + cookie.setAttribute("SameSite", responseCookie.getSameSite()); + } + cookie.setSecure(responseCookie.isSecure()); + cookie.setHttpOnly(responseCookie.isHttpOnly()); + if (responseCookie.isPartitioned()) { + cookie.setAttribute("Partitioned", ""); + } + cookies.add(cookie); + } + } + return cookies.toArray(new Cookie[0]); + } + + /** + * 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. + *

Examples:


+	 * // Check that the response body is equal to "Hello World":
+	 * assertThat(response).bodyText().isEqualTo("Hello World");
+	 * 
+ */ + public AbstractStringAssert bodyText() { + return Assertions.assertThat(readBody()); + } + + /** + * Verify that the response body is equal to the given value. + */ + public RestTestClientResponseAssert 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. + *

Examples:


+	 * // 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");
+	 * 
+ * The returned assert object also supports JSON path expressions. + *

Examples:


+	 * // 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");
+	 * 
+ */ + public AbstractJsonContentAssert bodyJson() { + return new JsonContentAssert(new JsonContent(readBody(), getExchangeResult().getMessageContentConverter())); + } + + 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(); + } + +} diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/client/assertj/package-info.java b/spring-test/src/main/java/org/springframework/test/web/servlet/client/assertj/package-info.java new file mode 100644 index 00000000000..918756c474a --- /dev/null +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/client/assertj/package-info.java @@ -0,0 +1,7 @@ +/** + * AssertJ support for RestTestClient. + */ +@NullMarked +package org.springframework.test.web.servlet.client.assertj; + +import org.jspecify.annotations.NullMarked; diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/client/assertj/RestTestClientResponseTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/client/assertj/RestTestClientResponseTests.java new file mode 100644 index 00000000000..1e61eb22109 --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/client/assertj/RestTestClientResponseTests.java @@ -0,0 +1,129 @@ +/* + * 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.servlet.client.assertj; + + +import java.util.Map; + +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletResponse; +import org.junit.jupiter.api.Test; + +import org.springframework.http.HttpHeaders; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverters; +import org.springframework.test.web.servlet.client.RestTestClient; +import org.springframework.test.web.servlet.client.RestTestClient.ResponseSpec; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.assertj.core.api.Assertions.assertThat; + +/** + * Unit tests for {@link RestTestClientResponse}. + * + * @author Rossen Stoyanchev + */ +public class RestTestClientResponseTests { + + private final RestTestClient client = + RestTestClient.bindToController(HelloController.class) + .configureMessageConverters(HttpMessageConverters.Builder::registerDefaults) + .build(); + + + @Test + void status() { + ResponseSpec spec = client.get().uri("/greeting").exchange(); + assertThat(RestTestClientResponse.from(spec)).hasStatusOk().hasStatus2xxSuccessful(); + } + + @Test + void headers() { + RestTestClient.ResponseSpec spec = client.get().uri("/greeting").exchange(); + + RestTestClientResponse response = RestTestClientResponse.from(spec); + assertThat(response).hasStatusOk(); + assertThat(response).headers() + .containsOnlyHeaders(HttpHeaders.CONTENT_TYPE, HttpHeaders.CONTENT_LENGTH) + .hasValue(HttpHeaders.CONTENT_TYPE, "text/plain;charset=ISO-8859-1") + .hasValue(HttpHeaders.CONTENT_LENGTH, 11); + } + + @Test + void contentType() { + ResponseSpec spec = client.get().uri("/greeting").exchange(); + + RestTestClientResponse response = RestTestClientResponse.from(spec); + assertThat(response).hasStatusOk(); + assertThat(response).contentType().isEqualTo("text/plain;charset=ISO-8859-1"); + assertThat(response).hasContentTypeCompatibleWith(MediaType.TEXT_PLAIN); + } + + @Test + void cookies() { + ResponseSpec spec = client.get().uri("/cookie").exchange(); + + RestTestClientResponse response = RestTestClientResponse.from(spec); + assertThat(response).hasStatusOk(); + assertThat(response).cookies().hasValue("foo", "bar"); + assertThat(response).body().isEmpty(); + } + + @Test + void bodyText() { + ResponseSpec spec = client.get().uri("/greeting").exchange(); + + RestTestClientResponse response = RestTestClientResponse.from(spec); + assertThat(response).hasStatusOk(); + assertThat(response).contentType().isCompatibleWith(MediaType.TEXT_PLAIN); + assertThat(response).bodyText().isEqualTo("Hello World"); + assertThat(response).hasBodyTextEqualTo("Hello World"); + } + + @Test + void bodyJson() { + ResponseSpec spec = client.get().uri("/message").exchange(); + + RestTestClientResponse response = RestTestClientResponse.from(spec); + assertThat(response).hasStatusOk(); + assertThat(response).contentType().isEqualTo(MediaType.APPLICATION_JSON); + assertThat(response).bodyJson().extractingPath("$.message").asString().isEqualTo("Hello World"); + } + + + @SuppressWarnings("unused") + @RestController + private static class HelloController { + + @GetMapping("/greeting") + public String getGreeting() { + return "Hello World"; + } + + @GetMapping("/message") + public Map getMessage() { + return Map.of("message", "Hello World"); + } + + @GetMapping("/cookie") + public void getCookie(HttpServletResponse response) { + response.addCookie(new Cookie("foo", "bar")); + } + } + +}