Remove Hamcrest use from RestTestClient

Closes gh-35702
This commit is contained in:
rstoyanchev
2025-10-29 08:46:27 +00:00
parent 92a186b44b
commit b4c6300ac6
24 changed files with 295 additions and 208 deletions
@@ -16,10 +16,14 @@
package org.springframework.test.web.reactive.server;
import org.hamcrest.Matcher;
import org.springframework.http.ResponseCookie;
import org.springframework.test.web.support.AbstractCookieAssertions;
import org.springframework.util.MultiValueMap;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* Assertions on cookies of the response.
*
@@ -45,4 +49,56 @@ public class CookieAssertions extends AbstractCookieAssertions<ExchangeResult, W
getExchangeResult().assertWithDiagnostics(assertion);
}
/**
* Assert the value of the response cookie with the given name with a Hamcrest
* {@link Matcher}.
*/
public WebTestClient.ResponseSpec value(String name, Matcher<? super String> matcher) {
String value = getCookie(name).getValue();
assertWithDiagnostics(() -> {
String message = getMessage(name);
assertThat(message, value, matcher);
});
return getResponseSpec();
}
/**
* Assert a cookie's "Max-Age" attribute with a Hamcrest {@link Matcher}.
*/
public WebTestClient.ResponseSpec maxAge(String name, Matcher<? super Long> matcher) {
long maxAge = getCookie(name).getMaxAge().getSeconds();
assertWithDiagnostics(() -> {
String message = getMessage(name) + " maxAge";
assertThat(message, maxAge, matcher);
});
return getResponseSpec();
}
/**
* Assert a cookie's "Path" attribute with a Hamcrest {@link Matcher}.
*/
public WebTestClient.ResponseSpec path(String name, Matcher<? super String> matcher) {
String path = getCookie(name).getPath();
assertWithDiagnostics(() -> {
String message = getMessage(name) + " path";
assertThat(message, path, matcher);
});
return getResponseSpec();
}
/**
* Assert a cookie's "Domain" attribute with a Hamcrest {@link Matcher}.
*/
public WebTestClient.ResponseSpec domain(String name, Matcher<? super String> matcher) {
String domain = getCookie(name).getDomain();
assertWithDiagnostics(() -> {
String message = getMessage(name) + " domain";
assertThat(message, domain, matcher);
});
return getResponseSpec();
}
}
@@ -16,9 +16,15 @@
package org.springframework.test.web.reactive.server;
import java.util.List;
import org.hamcrest.Matcher;
import org.springframework.http.HttpHeaders;
import org.springframework.test.web.support.AbstractHeaderAssertions;
import static org.hamcrest.MatcherAssert.assertThat;
/**
* Assertions on headers of the response.
*
@@ -47,4 +53,35 @@ public class HeaderAssertions extends AbstractHeaderAssertions<ExchangeResult, W
getExchangeResult().assertWithDiagnostics(assertion);
}
/**
* Assert the first value of the response header with a Hamcrest {@link Matcher}.
* @param name the header name
* @param matcher the matcher to use
*/
public WebTestClient.ResponseSpec value(String name, Matcher<? super String> matcher) {
String value = getResponseHeaders().getFirst(name);
assertWithDiagnostics(() -> {
String message = getMessage(name);
assertThat(message, value, matcher);
});
return getResponseSpec();
}
/**
* Assert all values of the response header with a Hamcrest {@link Matcher}.
* @param name the header name
* @param matcher the matcher to use
*/
public WebTestClient.ResponseSpec values(String name, Matcher<? super Iterable<String>> matcher) {
List<String> values = getResponseHeaders().get(name);
assertWithDiagnostics(() -> {
String message = getMessage(name);
assertThat(message, values, matcher);
});
return getResponseSpec();
}
}
@@ -17,8 +17,10 @@
package org.springframework.test.web.reactive.server;
import com.jayway.jsonpath.Configuration;
import org.hamcrest.Matcher;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.test.util.JsonPathExpectationsHelper;
import org.springframework.test.web.support.AbstractJsonPathAssertions;
@@ -41,4 +43,31 @@ public class JsonPathAssertions extends AbstractJsonPathAssertions<WebTestClient
super(spec, content, expression, configuration);
}
/**
* Delegates to {@link JsonPathExpectationsHelper#assertValue(String, Matcher)}.
*/
public <T> WebTestClient.BodyContentSpec value(Matcher<? super T> matcher) {
getPathHelper().assertValue(getContent(), matcher);
return getBodySpec();
}
/**
* Delegates to {@link JsonPathExpectationsHelper#assertValue(String, Matcher, Class)}.
*/
public <T> WebTestClient.BodyContentSpec value(Class<T> targetType, Matcher<? super T> matcher) {
getPathHelper().assertValue(getContent(), matcher, targetType);
return getBodySpec();
}
/**
* Delegates to {@link JsonPathExpectationsHelper#assertValue(String, Matcher, ParameterizedTypeReference)}.
*/
public <T> WebTestClient.BodyContentSpec value(ParameterizedTypeReference<T> targetType, Matcher<? super T> matcher) {
getPathHelper().assertValue(getContent(), matcher, targetType);
return getBodySpec();
}
}
@@ -16,6 +16,9 @@
package org.springframework.test.web.reactive.server;
import org.hamcrest.Matcher;
import org.hamcrest.MatcherAssert;
import org.springframework.http.HttpStatusCode;
import org.springframework.test.web.support.AbstractStatusAssertions;
@@ -45,4 +48,14 @@ public class StatusAssertions extends AbstractStatusAssertions<ExchangeResult, W
getExchangeResult().assertWithDiagnostics(assertion);
}
/**
* Match the response status value with a Hamcrest matcher.
* @param matcher the matcher to use
*/
public WebTestClient.ResponseSpec value(Matcher<? super Integer> matcher) {
int actual = getStatus().value();
assertWithDiagnostics(() -> MatcherAssert.assertThat("Response status", actual, matcher));
return getResponseSpec();
}
}
@@ -19,9 +19,11 @@ package org.springframework.test.web.reactive.server;
import java.util.Map;
import java.util.Optional;
import org.hamcrest.Matcher;
import org.jspecify.annotations.Nullable;
import org.springframework.http.HttpHeaders;
import org.springframework.test.util.XpathExpectationsHelper;
import org.springframework.test.web.support.AbstractXpathAssertions;
import org.springframework.util.Assert;
@@ -55,4 +57,26 @@ public class XpathAssertions extends AbstractXpathAssertions<WebTestClient.BodyC
Assert.notNull(body, "Expected body content");
return body;
}
/**
* Delegates to {@link XpathExpectationsHelper#assertString(byte[], String, Matcher)}.
*/
public WebTestClient.BodyContentSpec string(Matcher<? super String> matcher){
return assertWith(() -> getXpathHelper().assertString(getContent(), getCharset(), matcher));
}
/**
* Delegates to {@link XpathExpectationsHelper#assertNumber(byte[], String, Matcher)}.
*/
public WebTestClient.BodyContentSpec number(Matcher<? super Double> matcher){
return assertWith(() -> getXpathHelper().assertNumber(getContent(), getCharset(), matcher));
}
/**
* Delegates to {@link XpathExpectationsHelper#assertNodeCount(byte[], String, Matcher)}.
*/
public WebTestClient.BodyContentSpec nodeCount(Matcher<? super Integer> matcher){
return assertWith(() -> getXpathHelper().assertNodeCount(getContent(), getCharset(), matcher));
}
}
@@ -28,8 +28,6 @@ import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Function;
import org.hamcrest.Matcher;
import org.hamcrest.MatcherAssert;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ParameterizedTypeReference;
@@ -379,27 +377,21 @@ class DefaultRestTestClient implements RestTestClient {
return self();
}
@Override
public <T extends S> T value(Matcher<? super @Nullable B> matcher) {
this.result.assertWithDiagnostics(() -> MatcherAssert.assertThat(this.result.getResponseBody(), matcher));
return self();
}
@Override
public <T extends S, R> T value(Function<@Nullable B, @Nullable R> bodyMapper, Matcher<? super @Nullable R> matcher) {
this.result.assertWithDiagnostics(() -> {
B body = this.result.getResponseBody();
MatcherAssert.assertThat(bodyMapper.apply(body), matcher);
});
return self();
}
@Override
public <T extends S> T value(Consumer<@Nullable B> consumer) {
this.result.assertWithDiagnostics(() -> consumer.accept(this.result.getResponseBody()));
return self();
}
@Override
public <T extends S, R> T value(Function<@Nullable B, @Nullable R> bodyMapper, Consumer<? super @Nullable R> consumer) {
this.result.assertWithDiagnostics(() -> {
B body = this.result.getResponseBody();
consumer.accept(bodyMapper.apply(body));
});
return self();
}
@Override
public <T extends S> T consumeWith(Consumer<EntityExchangeResult<B>> consumer) {
this.result.assertWithDiagnostics(() -> consumer.accept(this.result));
@@ -24,7 +24,6 @@ import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;
import org.hamcrest.Matcher;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ParameterizedTypeReference;
@@ -681,22 +680,17 @@ public interface RestTestClient {
*/
<T extends S> T isEqualTo(@Nullable B expected);
/**
* Assert the extracted body with a {@link Matcher}.
*/
<T extends S> T value(Matcher<? super @Nullable B> matcher);
/**
* Transform the extracted the body with a function, for example, extracting a
* property, and assert the mapped value with a {@link Matcher}.
*/
<T extends S, R> T value(Function<@Nullable B, @Nullable R> bodyMapper, Matcher<? super @Nullable R> matcher);
/**
* Assert the extracted body with a {@link Consumer}.
*/
<T extends S> T value(Consumer<@Nullable B> consumer);
/**
* Transform the extracted the body with a function, for example, extracting a
* property, and assert the mapped value with a {@link Consumer}.
*/
<T extends S, R> T value(Function<@Nullable B, @Nullable R> bodyMapper, Consumer<? super @Nullable R> consumer);
/**
* Assert the exchange result with the given {@link Consumer}.
*/
@@ -19,14 +19,10 @@ package org.springframework.test.web.support;
import java.time.Duration;
import java.util.function.Consumer;
import org.hamcrest.Matcher;
import org.hamcrest.MatcherAssert;
import org.springframework.http.ResponseCookie;
import org.springframework.test.util.AssertionErrors;
import org.springframework.util.MultiValueMap;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.springframework.test.util.AssertionErrors.assertEquals;
import static org.springframework.test.util.AssertionErrors.fail;
@@ -59,6 +55,13 @@ public abstract class AbstractCookieAssertions<E, R> {
return this.exchangeResult;
}
/**
* Return the response spec.
*/
protected R getResponseSpec() {
return this.responseSpec;
}
/**
* Subclasses must implement this to provide access to response cookies.
*/
@@ -83,19 +86,6 @@ public abstract class AbstractCookieAssertions<E, R> {
return this.responseSpec;
}
/**
* Assert the value of the response cookie with the given name with a Hamcrest
* {@link Matcher}.
*/
public R value(String name, Matcher<? super String> matcher) {
String value = getCookie(name).getValue();
assertWithDiagnostics(() -> {
String message = getMessage(name);
MatcherAssert.assertThat(message, value, matcher);
});
return this.responseSpec;
}
/**
* Consume the value of the response cookie with the given name.
*/
@@ -138,14 +128,11 @@ public abstract class AbstractCookieAssertions<E, R> {
}
/**
* Assert a cookie's "Max-Age" attribute with a Hamcrest {@link Matcher}.
* Assert a cookie's "Max-Age" attribute with a {@link Consumer}.
*/
public R maxAge(String name, Matcher<? super Long> matcher) {
public R maxAge(String name, Consumer<Long> consumer) {
long maxAge = getCookie(name).getMaxAge().getSeconds();
assertWithDiagnostics(() -> {
String message = getMessage(name) + " maxAge";
assertThat(message, maxAge, matcher);
});
assertWithDiagnostics(() -> consumer.accept(maxAge));
return this.responseSpec;
}
@@ -161,17 +148,12 @@ public abstract class AbstractCookieAssertions<E, R> {
return this.responseSpec;
}
/**
* Assert a cookie's "Path" attribute with a Hamcrest {@link Matcher}.
* Assert a cookie's "Path" attribute with a {@link Consumer}.
*/
public R path(String name, Matcher<? super String> matcher) {
public R path(String name, Consumer<String> consumer) {
String path = getCookie(name).getPath();
assertWithDiagnostics(() -> {
String message = getMessage(name) + " path";
assertThat(message, path, matcher);
});
assertWithDiagnostics(() -> consumer.accept(path));
return this.responseSpec;
}
@@ -188,14 +170,11 @@ public abstract class AbstractCookieAssertions<E, R> {
}
/**
* Assert a cookie's "Domain" attribute with a Hamcrest {@link Matcher}.
* Assert a cookie's "Domain" attribute with a {@link Consumer}.
*/
public R domain(String name, Matcher<? super String> matcher) {
public R domain(String name, Consumer<String> consumer) {
String domain = getCookie(name).getDomain();
assertWithDiagnostics(() -> {
String message = getMessage(name) + " domain";
assertThat(message, domain, matcher);
});
assertWithDiagnostics(() -> consumer.accept(domain));
return this.responseSpec;
}
@@ -247,7 +226,7 @@ public abstract class AbstractCookieAssertions<E, R> {
return this.responseSpec;
}
private ResponseCookie getCookie(String name) {
protected ResponseCookie getCookie(String name) {
ResponseCookie cookie = getResponseCookies().getFirst(name);
if (cookie != null) {
return cookie;
@@ -258,7 +237,7 @@ public abstract class AbstractCookieAssertions<E, R> {
throw new IllegalStateException("This code path should not be reachable");
}
private static String getMessage(String cookie) {
protected String getMessage(String cookie) {
return "Response cookie '" + cookie + "'";
}
}
@@ -21,7 +21,6 @@ import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import org.hamcrest.Matcher;
import org.jspecify.annotations.Nullable;
import org.springframework.http.CacheControl;
@@ -30,7 +29,6 @@ import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.util.CollectionUtils;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.springframework.test.util.AssertionErrors.assertEquals;
import static org.springframework.test.util.AssertionErrors.assertNotNull;
import static org.springframework.test.util.AssertionErrors.assertTrue;
@@ -65,6 +63,10 @@ public abstract class AbstractHeaderAssertions <E, R> {
return this.exchangeResult;
}
protected R getResponseSpec() {
return this.responseSpec;
}
/**
* Subclasses must implement this to provide access to response headers.
*/
@@ -154,41 +156,13 @@ public abstract class AbstractHeaderAssertions <E, R> {
return this.responseSpec;
}
/**
* Assert the first value of the response header with a Hamcrest {@link Matcher}.
* @param name the header name
* @param matcher the matcher to use
*/
public R value(String name, Matcher<? super String> matcher) {
String value = getResponseHeaders().getFirst(name);
assertWithDiagnostics(() -> {
String message = getMessage(name);
assertThat(message, value, matcher);
});
return this.responseSpec;
}
/**
* Assert all values of the response header with a Hamcrest {@link Matcher}.
* @param name the header name
* @param matcher the matcher to use
*/
public R values(String name, Matcher<? super Iterable<String>> matcher) {
List<String> values = getResponseHeaders().get(name);
assertWithDiagnostics(() -> {
String message = getMessage(name);
assertThat(message, values, matcher);
});
return this.responseSpec;
}
/**
* Consume the first value of the named response header.
* @param name the header name
* @param consumer the consumer to use
*/
public R value(String name, Consumer<String> consumer) {
String value = getRequiredValue(name);
String value = getResponseHeaders().getFirst(name);
assertWithDiagnostics(() -> consumer.accept(value));
return this.responseSpec;
}
@@ -199,7 +173,7 @@ public abstract class AbstractHeaderAssertions <E, R> {
* @param consumer the consumer to use
*/
public R values(String name, Consumer<List<String>> consumer) {
List<String> values = getRequiredValues(name);
List<String> values = getResponseHeaders().get(name);
assertWithDiagnostics(() -> consumer.accept(values));
return this.responseSpec;
}
@@ -323,7 +297,7 @@ public abstract class AbstractHeaderAssertions <E, R> {
throw new IllegalStateException("This code path should not be reachable");
}
private static String getMessage(String headerName) {
protected String getMessage(String headerName) {
return "Response header '" + headerName + "'";
}
}
@@ -19,7 +19,6 @@ package org.springframework.test.web.support;
import java.util.function.Consumer;
import com.jayway.jsonpath.Configuration;
import org.hamcrest.Matcher;
import org.jspecify.annotations.Nullable;
import org.springframework.core.ParameterizedTypeReference;
@@ -55,6 +54,19 @@ public abstract class AbstractJsonPathAssertions<B> {
}
protected B getBodySpec() {
return this.bodySpec;
}
protected String getContent() {
return this.content;
}
protected JsonPathExpectationsHelper getPathHelper() {
return this.pathHelper;
}
/**
* Applies {@link JsonPathExpectationsHelper#assertValue(String, Object)}.
*/
@@ -143,30 +155,6 @@ public abstract class AbstractJsonPathAssertions<B> {
return this.bodySpec;
}
/**
* Delegates to {@link JsonPathExpectationsHelper#assertValue(String, Matcher)}.
*/
public <T> B value(Matcher<? super T> matcher) {
this.pathHelper.assertValue(this.content, matcher);
return this.bodySpec;
}
/**
* Delegates to {@link JsonPathExpectationsHelper#assertValue(String, Matcher, Class)}.
*/
public <T> B value(Class<T> targetType, Matcher<? super T> matcher) {
this.pathHelper.assertValue(this.content, matcher, targetType);
return this.bodySpec;
}
/**
* Delegates to {@link JsonPathExpectationsHelper#assertValue(String, Matcher, ParameterizedTypeReference)}.
*/
public <T> B value(ParameterizedTypeReference<T> targetType, Matcher<? super T> matcher) {
this.pathHelper.assertValue(this.content, matcher, targetType);
return this.bodySpec;
}
/**
* Consume the result of the JSONPath evaluation.
*/
@@ -18,9 +18,6 @@ package org.springframework.test.web.support;
import java.util.function.Consumer;
import org.hamcrest.Matcher;
import org.hamcrest.MatcherAssert;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpStatusCode;
import org.springframework.test.util.AssertionErrors;
@@ -56,6 +53,10 @@ public abstract class AbstractStatusAssertions<E, R> {
return this.exchangeResult;
}
protected R getResponseSpec() {
return this.responseSpec;
}
/**
* Subclasses must implement this to provide access to the response status.
*/
@@ -229,16 +230,6 @@ public abstract class AbstractStatusAssertions<E, R> {
return assertSeriesAndReturn(HttpStatus.Series.SERVER_ERROR);
}
/**
* Match the response status value with a Hamcrest matcher.
* @param matcher the matcher to use
*/
public R value(Matcher<? super Integer> matcher) {
int actual = getStatus().value();
assertWithDiagnostics(() -> MatcherAssert.assertThat("Response status", actual, matcher));
return this.responseSpec;
}
/**
* Consume the response status value as an integer.
* @param consumer the consumer to use
@@ -23,8 +23,8 @@ import java.util.function.Consumer;
import javax.xml.xpath.XPathExpressionException;
import org.hamcrest.Matcher;
import org.jspecify.annotations.Nullable;
import org.w3c.dom.NodeList;
import org.springframework.http.HttpHeaders;
import org.springframework.test.util.XpathExpectationsHelper;
@@ -71,6 +71,13 @@ public abstract class AbstractXpathAssertions<B> {
return this.bodySpec;
}
/**
* Return the XpathExpectationsHelper.
*/
protected XpathExpectationsHelper getXpathHelper() {
return this.xpathHelper;
}
/**
* Subclasses must implement this to provide access to response headers.
*/
@@ -124,27 +131,6 @@ public abstract class AbstractXpathAssertions<B> {
return assertWith(() -> this.xpathHelper.assertNodeCount(getContent(), getCharset(), expectedCount));
}
/**
* Delegates to {@link XpathExpectationsHelper#assertString(byte[], String, Matcher)}.
*/
public B string(Matcher<? super String> matcher){
return assertWith(() -> this.xpathHelper.assertString(getContent(), getCharset(), matcher));
}
/**
* Delegates to {@link XpathExpectationsHelper#assertNumber(byte[], String, Matcher)}.
*/
public B number(Matcher<? super Double> matcher){
return assertWith(() -> this.xpathHelper.assertNumber(getContent(), getCharset(), matcher));
}
/**
* Delegates to {@link XpathExpectationsHelper#assertNodeCount(byte[], String, Matcher)}.
*/
public B nodeCount(Matcher<? super Integer> matcher){
return assertWith(() -> this.xpathHelper.assertNodeCount(getContent(), getCharset(), matcher));
}
/**
* Consume the result of the XPath evaluation as a String.
*/
@@ -170,12 +156,13 @@ public abstract class AbstractXpathAssertions<B> {
*/
public B nodeCount(Consumer<Integer> consumer){
return assertWith(() -> {
Integer value = this.xpathHelper.evaluateXpath(getContent(), getCharset(), Integer.class);
NodeList nodeList = this.xpathHelper.evaluateXpath(getContent(), getCharset(), NodeList.class);
Integer value = (nodeList != null ? nodeList.getLength() : null);
consumer.accept(value);
});
}
private B assertWith(CheckedExceptionTask task) {
protected B assertWith(CheckedExceptionTask task) {
try {
task.run();
}
@@ -185,7 +172,7 @@ public abstract class AbstractXpathAssertions<B> {
return this.bodySpec;
}
private String getCharset() {
protected String getCharset() {
return getResponseHeaders()
.map(HttpHeaders::getContentType)
.map(MimeType::getCharset)
@@ -210,7 +197,7 @@ public abstract class AbstractXpathAssertions<B> {
* Lets us be able to use lambda expressions that could throw checked exceptions, since
* {@link XpathExpectationsHelper} throws {@link Exception} on its methods.
*/
private interface CheckedExceptionTask {
protected interface CheckedExceptionTask {
void run() throws Exception;