Polishing in [Rest|Web]TestClient Assertions

See gh-34428
This commit is contained in:
rstoyanchev
2025-07-29 21:09:20 +01:00
parent 6cc1310274
commit 88ddc9d45d
23 changed files with 561 additions and 1005 deletions
@@ -36,12 +36,13 @@ public class CookieAssertions extends AbstractCookieAssertions<ExchangeResult, W
@Override
protected void assertWithDiagnostics(Runnable assertion) {
exchangeResult.assertWithDiagnostics(assertion);
protected MultiValueMap<String, ResponseCookie> getResponseCookies() {
return getExchangeResult().getResponseCookies();
}
@Override
protected MultiValueMap<String, ResponseCookie> getResponseCookies() {
return exchangeResult.getResponseCookies();
protected void assertWithDiagnostics(Runnable assertion) {
getExchangeResult().assertWithDiagnostics(assertion);
}
}
@@ -38,12 +38,13 @@ public class HeaderAssertions extends AbstractHeaderAssertions<ExchangeResult, W
@Override
protected void assertWithDiagnostics(Runnable assertion) {
exchangeResult.assertWithDiagnostics(assertion);
protected HttpHeaders getResponseHeaders() {
return getExchangeResult().getResponseHeaders();
}
@Override
protected HttpHeaders getResponseHeaders() {
return exchangeResult.getResponseHeaders();
protected void assertWithDiagnostics(Runnable assertion) {
getExchangeResult().assertWithDiagnostics(assertion);
}
}
@@ -36,12 +36,13 @@ public class StatusAssertions extends AbstractStatusAssertions<ExchangeResult, W
@Override
protected void assertWithDiagnostics(Runnable assertion) {
exchangeResult.assertWithDiagnostics(assertion);
protected HttpStatusCode getStatus() {
return getExchangeResult().getStatus();
}
@Override
protected HttpStatusCode getStatus() {
return exchangeResult.getStatus();
protected void assertWithDiagnostics(Runnable assertion) {
getExchangeResult().assertWithDiagnostics(assertion);
}
}
@@ -35,20 +35,23 @@ import org.springframework.util.Assert;
*/
public class XpathAssertions extends AbstractXpathAssertions<WebTestClient.BodyContentSpec> {
XpathAssertions(WebTestClient.BodyContentSpec spec,
String expression, @Nullable Map<String, String> namespaces, Object... args) {
XpathAssertions(
WebTestClient.BodyContentSpec spec,
String expression, @Nullable Map<String, String> namespaces, Object... args) {
super(spec, expression, namespaces, args);
}
@Override
protected Optional<HttpHeaders> getResponseHeaders() {
return Optional.of(bodySpec.returnResult())
.map(ExchangeResult::getResponseHeaders);
return Optional.of(getBodySpec().returnResult()).map(ExchangeResult::getResponseHeaders);
}
@Override
protected byte[] getContent() {
byte[] body = this.bodySpec.returnResult().getResponseBody();
byte[] body = getBodySpec().returnResult().getResponseBody();
Assert.notNull(body, "Expected body content");
return body;
}
@@ -35,12 +35,13 @@ public class CookieAssertions extends AbstractCookieAssertions<ExchangeResult, R
@Override
protected void assertWithDiagnostics(Runnable assertion) {
exchangeResult.assertWithDiagnostics(assertion);
protected MultiValueMap<String, ResponseCookie> getResponseCookies() {
return getExchangeResult().getResponseCookies();
}
@Override
protected MultiValueMap<String, ResponseCookie> getResponseCookies() {
return exchangeResult.getResponseCookies();
protected void assertWithDiagnostics(Runnable assertion) {
getExchangeResult().assertWithDiagnostics(assertion);
}
}
@@ -23,6 +23,7 @@ import org.jspecify.annotations.Nullable;
* extracted to a representation of type {@code <T>}.
*
* @author Rob Worsnop
* @since 7.0
* @param <T> the response body type
*/
public class EntityExchangeResult<T> extends ExchangeResult {
@@ -33,13 +33,15 @@ public class HeaderAssertions extends AbstractHeaderAssertions<ExchangeResult, R
super(exchangeResult, responseSpec);
}
@Override
protected void assertWithDiagnostics(Runnable assertion) {
exchangeResult.assertWithDiagnostics(assertion);
}
@Override
protected HttpHeaders getResponseHeaders() {
return exchangeResult.getResponseHeaders();
return getExchangeResult().getResponseHeaders();
}
@Override
protected void assertWithDiagnostics(Runnable assertion) {
getExchangeResult().assertWithDiagnostics(assertion);
}
}
@@ -36,12 +36,13 @@ public class StatusAssertions extends AbstractStatusAssertions<ExchangeResult, R
@Override
protected void assertWithDiagnostics(Runnable assertion) {
exchangeResult.assertWithDiagnostics(assertion);
protected HttpStatusCode getStatus() {
return getExchangeResult().getStatus();
}
@Override
protected HttpStatusCode getStatus() {
return exchangeResult.getStatus();
protected void assertWithDiagnostics(Runnable assertion) {
getExchangeResult().assertWithDiagnostics(assertion);
}
}
@@ -44,13 +44,12 @@ public class XpathAssertions extends AbstractXpathAssertions<RestTestClient.Body
@Override
protected Optional<HttpHeaders> getResponseHeaders() {
return Optional.of(bodySpec.returnResult())
.map(ExchangeResult::getResponseHeaders);
return Optional.of(getBodySpec().returnResult()).map(ExchangeResult::getResponseHeaders);
}
@Override
protected byte[] getContent() {
byte[] body = this.bodySpec.returnResult().getResponseBody();
byte[] body = getBodySpec().returnResult().getResponseBody();
Assert.notNull(body, "Expected body content");
return body;
}
@@ -1,8 +1,7 @@
/**
* Support for testing Spring MVC applications via
* {@link org.springframework.test.web.reactive.server.WebTestClient}
* with {@link org.springframework.test.web.servlet.MockMvc} for server request
* handling.
* {@link org.springframework.test.web.servlet.client.RestTestClient} with
* {@link org.springframework.test.web.servlet.MockMvc} for server request handling.
*/
@NullMarked
@@ -34,19 +34,42 @@ import static org.springframework.test.util.AssertionErrors.fail;
* Assertions on cookies of the response.
*
* @author Rob Worsnop
* @author Rossen Stoyanchev
* @since 7.0
* @param <E> the type of the exchange result
* @param <R> the type of the response spec
*/
public abstract class AbstractCookieAssertions<E, R> {
protected final E exchangeResult;
private final E exchangeResult;
private final R responseSpec;
protected AbstractCookieAssertions(E exchangeResult, R responseSpec) {
this.exchangeResult = exchangeResult;
this.responseSpec = responseSpec;
}
/**
* Return the exchange result.
*/
protected E getExchangeResult() {
return this.exchangeResult;
}
/**
* Subclasses must implement this to provide access to response cookies.
*/
protected abstract MultiValueMap<String, ResponseCookie> getResponseCookies();
/**
* Subclasses must implement this to assert with diagnostics.
*/
protected abstract void assertWithDiagnostics(Runnable assertion);
/**
* Expect a response cookie with the given name to match the specified value.
*/
@@ -224,10 +247,6 @@ public abstract class AbstractCookieAssertions<E, R> {
return this.responseSpec;
}
protected abstract void assertWithDiagnostics(Runnable assertion);
protected abstract MultiValueMap<String, ResponseCookie> getResponseCookies();
private ResponseCookie getCookie(String name) {
ResponseCookie cookie = getResponseCookies().getFirst(name);
if (cookie != null) {
@@ -40,19 +40,42 @@ import static org.springframework.test.util.AssertionErrors.fail;
* Assertions on headers of the response.
*
* @author Rob Worsnop
* @author Rossen Stoyanchev
* @since 7.0
* @param <E> the type of the exchange result
* @param <R> the type of the response spec
*/
public abstract class AbstractHeaderAssertions <E, R> {
protected final E exchangeResult;
private final E exchangeResult;
private final R responseSpec;
protected AbstractHeaderAssertions(E exchangeResult, R responseSpec) {
this.exchangeResult = exchangeResult;
this.responseSpec = responseSpec;
}
/**
* Return the exchange result.
*/
protected E getExchangeResult() {
return this.exchangeResult;
}
/**
* Subclasses must implement this to provide access to response headers.
*/
protected abstract HttpHeaders getResponseHeaders();
/**
* Subclasses must implement this to assert with diagnostics.
*/
protected abstract void assertWithDiagnostics(Runnable assertion);
/**
* Expect a header with the given name to match the specified values.
*/
@@ -277,10 +300,6 @@ public abstract class AbstractHeaderAssertions <E, R> {
return assertHeader("Location", URI.create(location), getResponseHeaders().getLocation());
}
protected abstract void assertWithDiagnostics(Runnable assertion);
protected abstract HttpHeaders getResponseHeaders();
private R assertHeader(String name, @Nullable Object expected, @Nullable Object actual) {
assertWithDiagnostics(() -> {
String message = getMessage(name);
@@ -26,6 +26,18 @@ import org.springframework.core.ParameterizedTypeReference;
import org.springframework.test.util.JsonPathExpectationsHelper;
import org.springframework.util.Assert;
/**
* Base class for applying
* <a href="https://github.com/jayway/JsonPath">JsonPath</a> assertions
* in RestTestClient and WebTestClient.
*
* @author Rob Worsnop
* @author Rossen Stoyanchev
* @since 7.0
* @param <B> the type of body spec (RestTestClient vs WebTestClient specific)
* @see <a href="https://github.com/jayway/JsonPath">https://github.com/jayway/JsonPath</a>
* @see JsonPathExpectationsHelper
*/
public abstract class AbstractJsonPathAssertions<B> {
private final B bodySpec;
@@ -34,6 +46,7 @@ public abstract class AbstractJsonPathAssertions<B> {
private final JsonPathExpectationsHelper pathHelper;
protected AbstractJsonPathAssertions(B spec, String content, String expression, @Nullable Configuration configuration) {
Assert.hasText(expression, "expression must not be null or empty");
this.bodySpec = spec;
@@ -41,6 +54,7 @@ public abstract class AbstractJsonPathAssertions<B> {
this.pathHelper = new JsonPathExpectationsHelper(expression, configuration);
}
/**
* Applies {@link JsonPathExpectationsHelper#assertValue(String, Object)}.
*/
@@ -31,18 +31,42 @@ import static org.springframework.test.util.AssertionErrors.assertNotNull;
* Assertions on the response status.
*
* @author Rob Worsnop
* @author Rossen Stoyanchev
* @since 7.0
* @param <E> the type of the exchange result
* @param <R> the type of the response spec
*/
public abstract class AbstractStatusAssertions<E, R> {
protected final E exchangeResult;
private final E exchangeResult;
private final R responseSpec;
protected AbstractStatusAssertions(E exchangeResult, R responseSpec) {
this.exchangeResult = exchangeResult;
this.responseSpec = responseSpec;
}
/**
* Return the exchange result.
*/
protected E getExchangeResult() {
return this.exchangeResult;
}
/**
* Subclasses must implement this to provide access to the response status.
*/
protected abstract HttpStatusCode getStatus();
/**
* Subclasses must implement this to assert with diagnostics.
*/
protected abstract void assertWithDiagnostics(Runnable assertion);
/**
* Assert the response status as an {@link HttpStatusCode}.
*/
@@ -226,10 +250,6 @@ public abstract class AbstractStatusAssertions<E, R> {
return this.responseSpec;
}
protected abstract void assertWithDiagnostics(Runnable assertion);
protected abstract HttpStatusCode getStatus();
private R assertStatusAndReturn(HttpStatus expected) {
assertNotNull("exchangeResult unexpectedly null", this.exchangeResult);
HttpStatusCode actual = getStatus();
@@ -30,12 +30,24 @@ import org.springframework.http.HttpHeaders;
import org.springframework.test.util.XpathExpectationsHelper;
import org.springframework.util.MimeType;
/**
* Base class for applying XPath assertions in RestTestClient and WebTestClient.
*
* @author Rob Worsnop
* @author Rossen Stoyanchev
* @since 7.0
* @param <B> the type of body spec (RestTestClient vs WebTestClient specific)
*/
public abstract class AbstractXpathAssertions<B> {
protected final B bodySpec;
private final B bodySpec;
private final XpathExpectationsHelper xpathHelper;
public AbstractXpathAssertions(B spec, String expression, @Nullable Map<String, String> namespaces, Object... args) {
public AbstractXpathAssertions(
B spec, String expression, @Nullable Map<String, String> namespaces, Object... args) {
this.bodySpec = spec;
this.xpathHelper = initXpathHelper(expression, namespaces, args);
}
@@ -52,6 +64,24 @@ public abstract class AbstractXpathAssertions<B> {
}
/**
* Return the body spec.
*/
protected B getBodySpec() {
return this.bodySpec;
}
/**
* Subclasses must implement this to provide access to response headers.
*/
protected abstract Optional<HttpHeaders> getResponseHeaders();
/**
* Subclasses must implement this to provide access to the response content.
*/
protected abstract byte[] getContent();
/**
* Delegates to {@link XpathExpectationsHelper#assertString(byte[], String, String)}.
*/
@@ -175,9 +205,6 @@ public abstract class AbstractXpathAssertions<B> {
return super.hashCode();
}
protected abstract Optional<HttpHeaders> getResponseHeaders();
protected abstract byte[] getContent();
/**
* Lets us be able to use lambda expressions that could throw checked exceptions, since