Polish soft assertion support for WebTestClient

See gh-26969
This commit is contained in:
Sam Brannen
2021-08-23 18:59:41 +02:00
parent 25dca40413
commit 3c2dfebf4e
4 changed files with 106 additions and 52 deletions

View File

@@ -21,7 +21,6 @@ import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.time.Duration;
import java.time.ZonedDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.LinkedHashMap;
import java.util.List;
@@ -45,6 +44,7 @@ import org.springframework.http.client.reactive.ClientHttpConnector;
import org.springframework.http.client.reactive.ClientHttpRequest;
import org.springframework.lang.Nullable;
import org.springframework.test.util.AssertionErrors;
import org.springframework.test.util.ExceptionCollector;
import org.springframework.test.util.JsonExpectationsHelper;
import org.springframework.test.util.XmlExpectationsHelper;
import org.springframework.util.Assert;
@@ -64,6 +64,8 @@ import org.springframework.web.util.UriBuilderFactory;
* Default implementation of {@link WebTestClient}.
*
* @author Rossen Stoyanchev
* @author Sam Brannen
* @author Michał Rowicki
* @since 5.0
*/
class DefaultWebTestClient implements WebTestClient {
@@ -510,19 +512,25 @@ class DefaultWebTestClient implements WebTestClient {
}
@Override
public ResponseSpec expectAllSoftly(ResponseSpecMatcher... asserts) {
List<String> failedMessages = new ArrayList<>();
for (int i = 0; i < asserts.length; i++) {
ResponseSpecMatcher anAssert = asserts[i];
try {
anAssert.accept(this);
}
catch (AssertionError assertionException) {
failedMessages.add("[" + i + "] " + assertionException.getMessage());
}
public ResponseSpec expectAll(ResponseSpecConsumer... consumers) {
ExceptionCollector exceptionCollector = new ExceptionCollector();
for (ResponseSpecConsumer consumer : consumers) {
exceptionCollector.execute(() -> consumer.accept(this));
}
if (!failedMessages.isEmpty()) {
throw new AssertionError(String.join("\n", failedMessages));
try {
exceptionCollector.assertEmpty();
}
catch (RuntimeException ex) {
throw ex;
}
catch (Exception ex) {
// In theory, a ResponseSpecConsumer should never throw an Exception
// that is not a RuntimeException, but since ExceptionCollector may
// throw a checked Exception, we handle this to appease the compiler
// and in case someone uses a "sneaky throws" technique.
AssertionError assertionError = new AssertionError(ex.getMessage());
assertionError.initCause(ex);
throw assertionError;
}
return this;
}

View File

@@ -86,6 +86,8 @@ import org.springframework.web.util.UriBuilderFactory;
*
* @author Rossen Stoyanchev
* @author Brian Clozel
* @author Sam Brannen
* @author Michał Rowicki
* @since 5.0
* @see StatusAssertions
* @see HeaderAssertions
@@ -781,6 +783,34 @@ public interface WebTestClient {
*/
interface ResponseSpec {
/**
* Apply multiple assertions to a response with the given
* {@linkplain ResponseSpecConsumer consumers}, with the guarantee that
* all assertions will be applied even if one or more assertions fails
* with an exception.
* <p>If a single {@link Error} or {@link RuntimeException} is thrown,
* it will be rethrown.
* <p>If multiple exceptions are thrown, this method will throw an
* {@link AssertionError} whose error message is a summary of all of the
* exceptions. In addition, each exception will be added as a
* {@linkplain Throwable#addSuppressed(Throwable) suppressed exception} to
* the {@code AssertionError}.
* <p>This feature is similar to the {@code SoftAssertions} support in
* AssertJ and the {@code assertAll()} support in JUnit Jupiter.
*
* <h4>Example</h4>
* <pre class="code">
* webTestClient.get().uri("/hello").exchange()
* .expectAll(
* responseSpec -> responseSpec.expectStatus().isOk(),
* responseSpec -> responseSpec.expectBody(String.class).isEqualTo("Hello, World!")
* );
* </pre>
* @param consumers the list of {@code ResponseSpec} consumers
* @since 5.3.10
*/
ResponseSpec expectAll(ResponseSpecConsumer... consumers);
/**
* Assertions on the response status.
*/
@@ -847,9 +877,14 @@ public interface WebTestClient {
<T> FluxExchangeResult<T> returnResult(ParameterizedTypeReference<T> elementTypeRef);
/**
* Array of assertions to test together a.k.a. soft assertions.
* {@link Consumer} of a {@link ResponseSpec}.
* @since 5.3.10
* @see ResponseSpec#expectAll(ResponseSpecConsumer...)
*/
ResponseSpec expectAllSoftly(ResponseSpecMatcher... asserts);
@FunctionalInterface
interface ResponseSpecConsumer extends Consumer<ResponseSpec> {
}
}
@@ -1011,5 +1046,4 @@ public interface WebTestClient {
EntityExchangeResult<byte[]> returnResult();
}
interface ResponseSpecMatcher extends Consumer<ResponseSpec> {}
}