Update RestTestClient ExchangeResult

to expose request and URI template information and to have toString

See gh-34428
This commit is contained in:
rstoyanchev
2025-07-30 07:13:48 +01:00
parent 34f259778e
commit 862ffee385
5 changed files with 104 additions and 10 deletions
@@ -118,6 +118,8 @@ class DefaultRestTestClient implements RestTestClient {
private final RestClient.RequestBodyUriSpec requestHeadersUriSpec;
private @Nullable String uriTemplate;
DefaultRequestBodyUriSpec(RestClient.RequestBodyUriSpec spec) {
this.requestHeadersUriSpec = spec;
String requestId = String.valueOf(requestIndex.incrementAndGet());
@@ -126,24 +128,28 @@ class DefaultRestTestClient implements RestTestClient {
@Override
public RequestBodySpec uri(String uriTemplate, @Nullable Object... uriVariables) {
this.uriTemplate = uriTemplate;
this.requestHeadersUriSpec.uri(uriTemplate, uriVariables);
return this;
}
@Override
public RequestBodySpec uri(String uri, Map<String, ?> uriVariables) {
this.uriTemplate = uri;
this.requestHeadersUriSpec.uri(uri, uriVariables);
return this;
}
@Override
public RequestBodySpec uri(Function<UriBuilder, URI> uriFunction) {
this.uriTemplate = null;
this.requestHeadersUriSpec.uri(uriFunction);
return this;
}
@Override
public RequestBodySpec uri(URI uri) {
this.uriTemplate = null;
this.requestHeadersUriSpec.uri(uri);
return this;
}
@@ -229,8 +235,8 @@ class DefaultRestTestClient implements RestTestClient {
@Override
public ResponseSpec exchange() {
return new DefaultResponseSpec(
this.requestHeadersUriSpec.exchangeForRequiredValue(
(request, response) -> new ExchangeResult(response), false));
this.requestHeadersUriSpec.exchangeForRequiredValue((request, response) ->
new ExchangeResult(request, response, this.uriTemplate), false));
}
}
@@ -18,10 +18,12 @@ package org.springframework.test.web.servlet.client;
import java.io.IOException;
import java.net.HttpCookie;
import java.net.URI;
import java.util.List;
import java.util.Optional;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
@@ -29,6 +31,9 @@ import org.jspecify.annotations.Nullable;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.HttpRequest;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseCookie;
import org.springframework.util.Assert;
@@ -54,23 +59,60 @@ public class ExchangeResult {
private static final Log logger = LogFactory.getLog(ExchangeResult.class);
private final HttpRequest request;
private final ConvertibleClientHttpResponse clientResponse;
private final @Nullable String uriTemplate;
/** Ensure single logging; for example, for expectAll. */
private boolean diagnosticsLogged;
ExchangeResult(@Nullable ConvertibleClientHttpResponse response) {
Assert.notNull(response, "Response must not be null");
ExchangeResult(
HttpRequest request, ConvertibleClientHttpResponse response, @Nullable String uriTemplate) {
Assert.notNull(request, "HttpRequest must not be null");
Assert.notNull(response, "ClientHttpResponse must not be null");
this.request = request;
this.clientResponse = response;
this.uriTemplate = uriTemplate;
}
ExchangeResult(ExchangeResult result) {
this(result.clientResponse);
this(result.request, result.clientResponse, result.uriTemplate);
this.diagnosticsLogged = result.diagnosticsLogged;
}
/**
* Return the method of the request.
*/
public HttpMethod getMethod() {
return this.request.getMethod();
}
/**
* Return the URI of the request.
*/
public URI getUrl() {
return this.request.getURI();
}
/**
* Return the original URI template used to prepare the request, if any.
*/
public @Nullable String getUriTemplate() {
return this.uriTemplate;
}
/**
* Return the request headers sent to the server.
*/
public HttpHeaders getRequestHeaders() {
return this.request.getHeaders();
}
/**
* Return the HTTP status code as an {@link HttpStatusCode} value.
*/
@@ -146,4 +188,28 @@ public class ExchangeResult {
}
}
@Override
public String toString() {
return "\n" +
"> " + getMethod() + " " + getUrl() + "\n" +
"> " + formatHeaders(getRequestHeaders(), "\n> ") + "\n" +
"\n" +
"< " + formatStatus(getStatus()) + "\n" +
"< " + formatHeaders(getResponseHeaders(), "\n< ") + "\n";
}
private String formatStatus(HttpStatusCode statusCode) {
String result = statusCode.toString();
if (statusCode instanceof HttpStatus status) {
result += " " + status.getReasonPhrase();
}
return result;
}
private String formatHeaders(HttpHeaders headers, String delimiter) {
return headers.headerSet().stream()
.map(entry -> entry.getKey() + ": " + entry.getValue())
.collect(Collectors.joining(delimiter));
}
}
@@ -16,12 +16,16 @@
package org.springframework.test.web.servlet.client;
import java.io.IOException;
import java.time.Duration;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseCookie;
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
@@ -47,9 +51,14 @@ public class CookieAssertionsTests {
.sameSite("Lax")
.build();
private final CookieAssertions assertions = cookieAssertions(cookie);
private CookieAssertions assertions;
@BeforeEach
void setUp() throws IOException {
this.assertions = cookieAssertions(cookie);
}
@Test
void valueEquals() {
assertions.valueEquals("foo", "bar");
@@ -135,12 +144,13 @@ public class CookieAssertionsTests {
}
private CookieAssertions cookieAssertions(ResponseCookie cookie) {
private CookieAssertions cookieAssertions(ResponseCookie cookie) throws IOException {
RestClient.RequestHeadersSpec.ConvertibleClientHttpResponse response = mock();
var headers = new HttpHeaders();
headers.set(HttpHeaders.SET_COOKIE, cookie.toString());
when(response.getHeaders()).thenReturn(headers);
ExchangeResult result = new ExchangeResult(response);
when(response.getStatusCode()).thenReturn(HttpStatusCode.valueOf(200));
ExchangeResult result = new ExchangeResult(new MockClientHttpRequest(), response, null);
return new CookieAssertions(result, mock());
}
@@ -16,6 +16,7 @@
package org.springframework.test.web.servlet.client;
import java.io.IOException;
import java.net.URI;
import java.time.ZoneId;
import java.time.ZonedDateTime;
@@ -26,7 +27,9 @@ import org.junit.jupiter.api.Test;
import org.springframework.http.CacheControl;
import org.springframework.http.ContentDisposition;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.MediaType;
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
@@ -311,10 +314,16 @@ class HeaderAssertionTests {
}
private HeaderAssertions headerAssertions(HttpHeaders responseHeaders) {
try {
RestClient.RequestHeadersSpec.ConvertibleClientHttpResponse response = mock();
when(response.getStatusCode()).thenReturn(HttpStatusCode.valueOf(200));
when(response.getHeaders()).thenReturn(responseHeaders);
ExchangeResult result = new ExchangeResult(response);
ExchangeResult result = new ExchangeResult(new MockClientHttpRequest(), response, null);
return new HeaderAssertions(result, mock());
}
catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
}
@@ -20,8 +20,10 @@ import java.io.IOException;
import org.junit.jupiter.api.Test;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.mock.http.client.MockClientHttpRequest;
import org.springframework.web.client.RestClient;
import static org.assertj.core.api.Assertions.assertThat;
@@ -255,7 +257,8 @@ class StatusAssertionTests {
try {
RestClient.RequestHeadersSpec.ConvertibleClientHttpResponse response = mock();
when(response.getStatusCode()).thenReturn(HttpStatusCode.valueOf(status));
ExchangeResult result = new ExchangeResult(response);
when(response.getHeaders()).thenReturn(new HttpHeaders());
ExchangeResult result = new ExchangeResult(new MockClientHttpRequest(), response, null);
return new StatusAssertions(result, mock());
}
catch (IOException ex) {