diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/ResultActions.java b/spring-test/src/main/java/org/springframework/test/web/servlet/ResultActions.java
index d63a5dd4884e..bbcb0a2e127d 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/ResultActions.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/ResultActions.java
@@ -42,7 +42,7 @@ public interface ResultActions {
* .andExpect(jsonPath("$.person.name").value("Jason"));
*
*
- *
Or alternatively provide all matchers as a vararg:
+ *
Either provide all matchers as a vararg:
*
* static imports: MockMvcRequestBuilders.*, MockMvcResultMatchers.*, ResultMatcher.matchAll
*
@@ -56,6 +56,16 @@ public interface ResultActions {
* flash().attribute("message", "success!"))
* );
*
+ *
+ * Or provide all matchers to be evaluated no matter if one of them fail:
+ *
+ * static imports: MockMvcRequestBuilders.*, MockMvcResultMatchers.*, ResultMatcher.matchAllSoftly
+ * mockMvc.perform(post("/form"))
+ * .andExpect(matchAllSoftly(
+ * status().isOk(),
+ * redirectedUrl("/person/1")
+ * );
+ *
*/
ResultActions andExpect(ResultMatcher matcher) throws Exception;
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/ResultMatcher.java b/spring-test/src/main/java/org/springframework/test/web/servlet/ResultMatcher.java
index 1b6b409806d4..6c4d054d2359 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/ResultMatcher.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/ResultMatcher.java
@@ -16,6 +16,9 @@
package org.springframework.test.web.servlet;
+import java.util.ArrayList;
+import java.util.List;
+
/**
* A {@code ResultMatcher} matches the result of an executed request against
* some expectation.
@@ -70,4 +73,30 @@ static ResultMatcher matchAll(ResultMatcher... matchers) {
};
}
+ /**
+ * Static method for matching with an array of result matchers whose assertion failures are caught and stored.
+ * Only when all of them would be called a {@link AssertionError} be thrown containing the error messages of those
+ * previously caught assertion failures.
+ * @param matchers the matchers
+ * @author MichaĆ Rowicki
+ * @since 5.2
+ */
+ static ResultMatcher matchAllSoftly(ResultMatcher... matchers) {
+ return result -> {
+ List failedMessages = new ArrayList<>();
+ for (int i = 0; i < matchers.length; i++) {
+ ResultMatcher matcher = matchers[i];
+ try {
+ matcher.match(result);
+ }
+ catch (AssertionError assertionException) {
+ failedMessages.add("[" + i + "] " + assertionException.getMessage());
+ }
+ }
+ if (!failedMessages.isEmpty()) {
+ throw new AssertionError(String.join("\n", failedMessages));
+ }
+ };
+ }
+
}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/ResultMatcherTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/ResultMatcherTests.java
new file mode 100644
index 000000000000..40cbb56063f9
--- /dev/null
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/ResultMatcherTests.java
@@ -0,0 +1,66 @@
+/*
+ * 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.servlet;
+
+import org.jetbrains.annotations.NotNull;
+import org.junit.jupiter.api.Test;
+
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+import static org.assertj.core.api.Assertions.assertThatNoException;
+
+class ResultMatcherTests {
+
+ @Test
+ void whenProvidedMatcherPassesThenSoftAssertionsAlsoPasses() {
+ ResultMatcher resultMatcher = ResultMatcher.matchAllSoftly(this::doNothing);
+ StubMvcResult stubMvcResult = new StubMvcResult(null, null, null, null, null, null, null);
+
+ assertThatNoException().isThrownBy(() -> resultMatcher.match(stubMvcResult));
+ }
+
+ @Test
+ void whenOneOfMatcherFailsThenSoftAssertionFailsWithTheVerySameMessage() {
+ String failMessage = "fail message";
+ StubMvcResult stubMvcResult = new StubMvcResult(null, null, null, null, null, null, null);
+ ResultMatcher resultMatcher = ResultMatcher.matchAllSoftly(failMatcher(failMessage));
+
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> resultMatcher.match(stubMvcResult))
+ .withMessage("[0] " + failMessage);
+ }
+
+ @Test
+ void whenMultipleMatchersFailsThenSoftAssertionFailsWithOneErrorWithMessageContainingAllErrorMessagesWithTheSameOrder() {
+ String firstFail = "firstFail";
+ String secondFail = "secondFail";
+ StubMvcResult stubMvcResult = new StubMvcResult(null, null, null, null, null, null, null);
+ ResultMatcher resultMatcher = ResultMatcher.matchAllSoftly(failMatcher(firstFail), failMatcher(secondFail));
+
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> resultMatcher.match(stubMvcResult))
+ .withMessage("[0] " + firstFail + "\n[1] " + secondFail);
+ }
+
+ @NotNull
+ private ResultMatcher failMatcher(String failMessage) {
+ return result -> {
+ throw new AssertionError(failMessage);
+ };
+ }
+
+ void doNothing(MvcResult mvcResult) {}
+}