From 25dca404138e85e8864e4bfa4b37c88f236a559e Mon Sep 17 00:00:00 2001 From: Michal Rowicki Date: Sun, 23 May 2021 13:21:31 +0200 Subject: [PATCH] Introduce soft assertions for WebTestClient It happens very often that WebTestClient is used in heavyweight integration tests, and it's a hindrance to developer productivity to fix one failed assertion after another. Soft assertions help a lot by checking all conditions at once even if one of them fails. This commit introduces a new expectAllSoftly(..) method in WebTestClient to address this issue. client.get().uri("/hello") .exchange() .expectAllSoftly( spec -> spec.expectStatus().isOk(), spec -> spec.expectBody(String.class).isEqualTo("Hello, World") ); Closes gh-26969 --- .../reactive/server/DefaultWebTestClient.java | 19 +++++ .../web/reactive/server/WebTestClient.java | 6 ++ .../server/samples/SoftAssertionTests.java | 75 +++++++++++++++++++ 3 files changed, 100 insertions(+) create mode 100644 spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/SoftAssertionTests.java diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java index 8d0ec7de0c..06dab2ea38 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/DefaultWebTestClient.java @@ -21,6 +21,7 @@ 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; @@ -507,6 +508,24 @@ class DefaultWebTestClient implements WebTestClient { Flux body = this.response.bodyToFlux(elementTypeRef); return new FluxExchangeResult<>(this.exchangeResult, body); } + + @Override + public ResponseSpec expectAllSoftly(ResponseSpecMatcher... asserts) { + List 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()); + } + } + if (!failedMessages.isEmpty()) { + throw new AssertionError(String.join("\n", failedMessages)); + } + return this; + } } diff --git a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java index 6d2fcefebd..8f0159aa70 100644 --- a/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java +++ b/spring-test/src/main/java/org/springframework/test/web/reactive/server/WebTestClient.java @@ -845,6 +845,11 @@ public interface WebTestClient { * about a target type with generics. */ FluxExchangeResult returnResult(ParameterizedTypeReference elementTypeRef); + + /** + * Array of assertions to test together a.k.a. soft assertions. + */ + ResponseSpec expectAllSoftly(ResponseSpecMatcher... asserts); } @@ -1006,4 +1011,5 @@ public interface WebTestClient { EntityExchangeResult returnResult(); } + interface ResponseSpecMatcher extends Consumer {} } diff --git a/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/SoftAssertionTests.java b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/SoftAssertionTests.java new file mode 100644 index 0000000000..17d5cc1a3d --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/reactive/server/samples/SoftAssertionTests.java @@ -0,0 +1,75 @@ +/* + * Copyright 2002-2021 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.reactive.server.samples; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import org.springframework.test.web.reactive.server.WebTestClient; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; + +/** + * Samples of tests using {@link WebTestClient} with soft assertions. + * + * @author MichaƂ Rowicki + * @since 5.3 + */ +public class SoftAssertionTests { + + private WebTestClient client; + + + @BeforeEach + public void setUp() throws Exception { + this.client = WebTestClient.bindToController(new TestController()).build(); + } + + + @Test + public void test() throws Exception { + this.client.get().uri("/test") + .exchange() + .expectAllSoftly( + exchange -> exchange.expectStatus().isOk(), + exchange -> exchange.expectBody(String.class).isEqualTo("It works!") + ); + } + + @Test + public void testAllFails() throws Exception { + assertThatExceptionOfType(AssertionError.class).isThrownBy(() -> + this.client.get().uri("/test") + .exchange() + .expectAllSoftly( + exchange -> exchange.expectStatus().isBadRequest(), + exchange -> exchange.expectBody(String.class).isEqualTo("It won't work :(") + ) + ).withMessage("[0] Status expected:<400 BAD_REQUEST> but was:<200 OK>\n[1] Response body expected: but was:"); + } + + + @RestController + static class TestController { + + @GetMapping("/test") + public String handle() { + return "It works!"; + } + } +}