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 8259a19508..f4fd6da14b 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 @@ -16,6 +16,8 @@ package org.springframework.test.web.servlet; +import org.springframework.test.util.ExceptionCollector; + /** * Allows applying actions, such as expectations, on the result of an executed * request. @@ -25,6 +27,8 @@ package org.springframework.test.web.servlet; * {@link org.springframework.test.web.servlet.result.MockMvcResultHandlers}. * * @author Rossen Stoyanchev + * @author Sam Brannen + * @author Michał Rowicki * @since 3.2 */ public interface ResultActions { @@ -32,9 +36,9 @@ public interface ResultActions { /** * Perform an expectation. * - *
You can invoke {@code andExpect()} multiple times. + *
You can invoke {@code andExpect()} multiple times as in the following + * example. *
* // static imports: MockMvcRequestBuilders.*, MockMvcResultMatchers.*
*
@@ -44,34 +48,49 @@ public interface ResultActions {
* .andExpect(jsonPath("$.person.name").value("Jason"));
*
*
- * You can provide all matchers as a var-arg list with {@code matchAll()}. - *
- * // static imports: MockMvcRequestBuilders.*, MockMvcResultMatchers.*, ResultMatcher.matchAll
- *
- * mockMvc.perform(post("/form"))
- * .andExpect(matchAll(
- * status().isOk(),
- * redirectedUrl("/person/1"),
- * model().size(1),
- * model().attributeExists("person"),
- * flash().attributeCount(1),
- * flash().attribute("message", "success!"))
- * );
- *
- *
- * Alternatively, you can provide all matchers to be evaluated using - * soft assertions with {@code matchAllSoftly()}. - *
- * // static imports: MockMvcRequestBuilders.*, MockMvcResultMatchers.*, ResultMatcher.matchAllSoftly
- * mockMvc.perform(post("/form"))
- * .andExpect(matchAllSoftly(
- * status().isOk(),
- * redirectedUrl("/person/1"))
- * );
- *
+ * @see #andExpectAll(ResultMatcher...)
*/
ResultActions andExpect(ResultMatcher matcher) throws Exception;
+ /**
+ * Perform multiple expectations, with the guarantee that all expectations
+ * will be asserted even if one or more expectations fail with an exception.
+ * If a single {@link Error} or {@link Exception} is thrown, it will + * be rethrown. + *
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}. + *
This feature is similar to the {@code SoftAssertions} support in AssertJ + * and the {@code assertAll()} support in JUnit Jupiter. + * + *
Instead of invoking {@code andExpect()} multiple times, you can invoke + * {@code andExpectAll()} as in the following example. + *
+ * // static imports: MockMvcRequestBuilders.*, MockMvcResultMatchers.*
+ *
+ * mockMvc.perform(get("/person/1"))
+ * .andExpectAll(
+ * status().isOk(),
+ * content().contentType(MediaType.APPLICATION_JSON),
+ * jsonPath("$.person.name").value("Jason")
+ * );
+ *
+ *
+ * @since 5.3.10
+ * @see #andExpect(ResultMatcher)
+ */
+ default ResultActions andExpectAll(ResultMatcher... matchers) throws Exception {
+ ExceptionCollector exceptionCollector = new ExceptionCollector();
+ for (ResultMatcher matcher : matchers) {
+ exceptionCollector.execute(() -> this.andExpect(matcher));
+ }
+ exceptionCollector.assertEmpty();
+ return this;
+ }
+
/**
* Perform a general action.
*
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 ac40b9d716..44aae68e15 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,8 +16,6 @@
package org.springframework.test.web.servlet;
-import org.springframework.test.util.ExceptionCollector;
-
/**
* A {@code ResultMatcher} matches the result of an executed request against
* some expectation.
@@ -40,13 +38,13 @@ import org.springframework.test.util.ExceptionCollector;
* MockMvc mockMvc = webAppContextSetup(wac).build();
*
* mockMvc.perform(get("/form"))
- * .andExpect(status().isOk())
- * .andExpect(content().mimeType(MediaType.APPLICATION_JSON));
+ * .andExpectAll(
+ * status().isOk(),
+ * content().mimeType(MediaType.APPLICATION_JSON));
*
*
* @author Rossen Stoyanchev
* @author Sam Brannen
- * @author Michał Rowicki
* @since 3.2
*/
@FunctionalInterface
@@ -64,7 +62,10 @@ public interface ResultMatcher {
* Static method for matching with an array of result matchers.
* @param matchers the matchers
* @since 5.1
+ * @deprecated as of Spring Framework 5.3.10, in favor of
+ * {@link ResultActions#andExpectAll(ResultMatcher...)}
*/
+ @Deprecated
static ResultMatcher matchAll(ResultMatcher... matchers) {
return result -> {
for (ResultMatcher matcher : matchers) {
@@ -73,22 +74,4 @@ public interface ResultMatcher {
};
}
- /**
- * Static method for matching with an array of result matchers whose assertion
- * failures are caught and stored. Once all matchers have been called, if any
- * failures occurred, an {@link AssertionError} will be thrown containing the
- * error messages of all assertion failures.
- * @param matchers the matchers
- * @since 5.3.10
- */
- static ResultMatcher matchAllSoftly(ResultMatcher... matchers) {
- return result -> {
- ExceptionCollector exceptionCollector = new ExceptionCollector();
- for (ResultMatcher matcher : matchers) {
- exceptionCollector.execute(() -> matcher.match(result));
- }
- exceptionCollector.assertEmpty();
- };
- }
-
}
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
deleted file mode 100644
index dd7cd3527c..0000000000
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/ResultMatcherTests.java
+++ /dev/null
@@ -1,117 +0,0 @@
-/*
- * 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.junit.jupiter.api.Test;
-
-import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
-import static org.assertj.core.api.Assertions.assertThatNoException;
-
-/**
- * Unit tests for {@link ResultMatcher}.
- *
- * @author Michał Rowicki
- * @author Sam Brannen
- * @since 5.3.10
- */
-class ResultMatcherTests {
-
- private static final String EOL = "\n";
-
- private final StubMvcResult stubMvcResult = new StubMvcResult(null, null, null, null, null, null, null);
-
-
- @Test
- void softAssertionsWithNoFailures() {
- ResultMatcher resultMatcher = ResultMatcher.matchAllSoftly(this::doNothing);
-
- assertThatNoException().isThrownBy(() -> resultMatcher.match(stubMvcResult));
- }
-
- @Test
- void softAssertionsWithOneAssertionError() {
- String failureMessage = "error";
- ResultMatcher resultMatcher = ResultMatcher.matchAllSoftly(assertionErrorMatcher(failureMessage));
-
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> resultMatcher.match(stubMvcResult))
- .withMessage(failureMessage)
- .withNoCause()
- .satisfies(error -> assertThat(error).hasNoSuppressedExceptions());
- }
-
- @Test
- void softAssertionsWithOneRuntimeException() {
- String failureMessage = "exception";
- ResultMatcher resultMatcher = ResultMatcher.matchAllSoftly(uncheckedExceptionMatcher(failureMessage));
-
- assertThatExceptionOfType(RuntimeException.class)
- .isThrownBy(() -> resultMatcher.match(stubMvcResult))
- .withMessage(failureMessage)
- .withNoCause()
- .satisfies(error -> assertThat(error).hasNoSuppressedExceptions());
- }
-
- @Test
- void softAssertionsWithOneCheckedException() {
- String failureMessage = "exception";
- ResultMatcher resultMatcher = ResultMatcher.matchAllSoftly(checkedExceptionMatcher(failureMessage));
-
- assertThatExceptionOfType(Exception.class)
- .isThrownBy(() -> resultMatcher.match(stubMvcResult))
- .withMessage(failureMessage)
- .withNoCause()
- .satisfies(exception -> assertThat(exception).hasNoSuppressedExceptions());
- }
-
- @Test
- void softAssertionsWithTwoFailures() {
- String firstFailure = "firstFailure";
- String secondFailure = "secondFailure";
- String thirdFailure = "thirdFailure";
- ResultMatcher resultMatcher = ResultMatcher.matchAllSoftly(assertionErrorMatcher(firstFailure),
- checkedExceptionMatcher(secondFailure), uncheckedExceptionMatcher(thirdFailure));
-
- assertThatExceptionOfType(AssertionError.class)
- .isThrownBy(() -> resultMatcher.match(stubMvcResult))
- .withMessage("Multiple Exceptions (3):" + EOL + firstFailure + EOL + secondFailure + EOL + thirdFailure)
- .satisfies(error -> assertThat(error.getSuppressed()).hasSize(3));
- }
-
- private ResultMatcher assertionErrorMatcher(String failureMessage) {
- return result -> {
- throw new AssertionError(failureMessage);
- };
- }
-
- private ResultMatcher uncheckedExceptionMatcher(String failureMessage) {
- return result -> {
- throw new RuntimeException(failureMessage);
- };
- }
-
- private ResultMatcher checkedExceptionMatcher(String failureMessage) {
- return result -> {
- throw new Exception(failureMessage);
- };
- }
-
- void doNothing(MvcResult mvcResult) {
- }
-
-}
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/JavaConfigTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/JavaConfigTests.java
index e96c0c1f6c..9cde95fe2c 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/JavaConfigTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/JavaConfigTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2019 the original author or authors.
+ * 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.
@@ -47,11 +47,13 @@ import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.view.tiles3.TilesConfigurer;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.BDDMockito.given;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultHandlers.print;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.forwardedUrl;
+import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
@@ -61,6 +63,7 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.
* @author Rossen Stoyanchev
* @author Sam Brannen
* @author Sebastien Deleuze
+ * @author Michał Rowicki
*/
@ExtendWith(SpringExtension.class)
@WebAppConfiguration("classpath:META-INF/web-resources")
@@ -93,9 +96,38 @@ public class JavaConfigTests {
public void person() throws Exception {
this.mockMvc.perform(get("/person/5").accept(MediaType.APPLICATION_JSON))
.andDo(print())
- .andExpect(status().isOk())
- .andExpect(request().asyncNotStarted())
- .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
+ .andExpectAll(
+ status().isOk(),
+ request().asyncNotStarted(),
+ content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"),
+ jsonPath("$.name").value("Joe")
+ );
+ }
+
+ @Test
+ public void andExpectAllWithOneFailure() {
+ assertThatExceptionOfType(AssertionError.class)
+ .isThrownBy(() -> this.mockMvc.perform(get("/person/5").accept(MediaType.APPLICATION_JSON))
+ .andExpectAll(
+ status().isBadGateway(),
+ request().asyncNotStarted(),
+ jsonPath("$.name").value("Joe")))
+ .withMessage("Status expected:<502> but was:<200>")
+ .satisfies(error -> assertThat(error).hasNoSuppressedExceptions());
+ }
+
+ @Test
+ public void andExpectAllWithMultipleFailures() {
+ assertThatExceptionOfType(AssertionError.class).isThrownBy(() ->
+ this.mockMvc.perform(get("/person/5").accept(MediaType.APPLICATION_JSON))
+ .andExpectAll(
+ status().isBadGateway(),
+ request().asyncNotStarted(),
+ jsonPath("$.name").value("Joe"),
+ jsonPath("$.name").value("Jane")
+ ))
+ .withMessage("Multiple Exceptions (2):\nStatus expected:<502> but was:<200>\nJSON path \"$.name\" expected: