From 8d2bc3bdbabcfaf1ab4d0232f891bcf380ad975f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane=20Nicoll?= Date: Tue, 28 May 2024 11:08:13 +0200 Subject: [PATCH] Add support for fluent preparation of the request in MockMvcTester See gh-32913 --- .../web/servlet/assertj/MockMvcTester.java | 180 +++++++++++++++--- ...vcTesterCompatibilityIntegrationTests.java | 94 +++++++++ .../MockMvcTesterIntegrationTests.java | 110 ++++++----- 3 files changed, 306 insertions(+), 78 deletions(-) create mode 100644 spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterCompatibilityIntegrationTests.java diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MockMvcTester.java b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MockMvcTester.java index c0d4546e083..5dd258ee987 100644 --- a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MockMvcTester.java +++ b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MockMvcTester.java @@ -23,13 +23,18 @@ import java.util.Map; import java.util.function.Function; import java.util.stream.StreamSupport; +import org.assertj.core.api.AssertProvider; + +import org.springframework.http.HttpMethod; import org.springframework.http.MediaType; import org.springframework.http.converter.GenericHttpMessageConverter; import org.springframework.http.converter.HttpMessageConverter; import org.springframework.lang.Nullable; +import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.test.web.servlet.MockMvc; import org.springframework.test.web.servlet.MvcResult; import org.springframework.test.web.servlet.RequestBuilder; +import org.springframework.test.web.servlet.request.AbstractMockHttpServletRequestBuilder; import org.springframework.test.web.servlet.request.MockMvcRequestBuilders; import org.springframework.test.web.servlet.setup.DefaultMockMvcBuilder; import org.springframework.test.web.servlet.setup.MockMvcBuilders; @@ -58,11 +63,25 @@ import org.springframework.web.context.WebApplicationContext; * MockMvcTester mvc = MockMvcTester.of(new PersonController()); * * - *

Once a tester instance is available, you can perform requests in a similar - * fashion as with {@link MockMvc}, and wrapping the result in - * {@code assertThat()} provides access to assertions. For instance: + *

Simple, single-statement assertions can be done wrapping the request + * builder in {@code assertThat()} provides access to assertions. For instance: *


  * // perform a GET on /hi and assert the response body is equal to Hello
+ * assertThat(mvc.get().uri("/hi")).hasStatusOk().hasBodyTextEqualTo("Hello");
+ * 
+ * + *

For more complex scenarios the {@linkplain MvcTestResult result} of the + * exchange can be assigned in a variable to run multiple assertions: + *


+ * // perform a POST on /save and assert the response body is empty
+ * MvcTestResult result = mvc.post().uri("/save").exchange();
+ * assertThat(result).hasStatus(HttpStatus.CREATED);
+ * assertThat(result).body().isEmpty();
+ * 
+ * + *

You can also perform requests using the static builders approach that + * {@link MockMvc} uses. For instance:


+ * // perform a GET on /hi and assert the response body is equal to Hello
  * assertThat(mvc.perform(get("/hi")))
  *         .hasStatusOk().hasBodyTextEqualTo("Hello");
  * 
@@ -74,12 +93,11 @@ import org.springframework.web.context.WebApplicationContext; * which allows you to assert that a request failed unexpectedly: *

  * // perform a GET on /boom and assert the message for the the unresolved exception
- * assertThat(mvc.perform(get("/boom")))
- *         .hasUnresolvedException())
+ * assertThat(mvc.get().uri("/boom")).hasUnresolvedException())
  *         .withMessage("Test exception");
  * 
* - *

{@link MockMvcTester} can be configured with a list of + *

{@code MockMvcTester} can be configured with a list of * {@linkplain HttpMessageConverter message converters} to allow the response * body to be deserialized, rather than asserting on the raw values. * @@ -104,8 +122,7 @@ public final class MockMvcTester { } /** - * Create a {@link MockMvcTester} instance that delegates to the given - * {@link MockMvc} instance. + * Create an instance that delegates to the given {@link MockMvc} instance. * @param mockMvc the MockMvc instance to delegate calls to */ public static MockMvcTester create(MockMvc mockMvc) { @@ -113,9 +130,9 @@ public final class MockMvcTester { } /** - * Create an {@link MockMvcTester} instance using the given, fully - * initialized (i.e., refreshed) {@link WebApplicationContext}. The - * given {@code customizations} are applied to the {@link DefaultMockMvcBuilder} + * Create an instance using the given, fully initialized (i.e., + * refreshed) {@link WebApplicationContext}. The given + * {@code customizations} are applied to the {@link DefaultMockMvcBuilder} * that ultimately creates the underlying {@link MockMvc} instance. *

If no further customization of the underlying {@link MockMvc} instance * is required, use {@link #from(WebApplicationContext)}. @@ -134,8 +151,8 @@ public final class MockMvcTester { } /** - * Shortcut to create an {@link MockMvcTester} instance using the given, - * fully initialized (i.e., refreshed) {@link WebApplicationContext}. + * Shortcut to create an instance using the given fully initialized (i.e., + * refreshed) {@link WebApplicationContext}. *

Consider using {@link #from(WebApplicationContext, Function)} if * further customization of the underlying {@link MockMvc} instance is * required. @@ -148,9 +165,8 @@ public final class MockMvcTester { } /** - * Create an {@link MockMvcTester} instance by registering one or more - * {@code @Controller} instances and configuring Spring MVC infrastructure - * programmatically. + * Create an instance by registering one or more {@code @Controller} instances + * and configuring Spring MVC infrastructure programmatically. *

This allows full control over the instantiation and initialization of * controllers and their dependencies, similar to plain unit tests while * also making it possible to test one controller at a time. @@ -170,8 +186,8 @@ public final class MockMvcTester { } /** - * Shortcut to create an {@link MockMvcTester} instance by registering one - * or more {@code @Controller} instances. + * Shortcut to create an instance by registering one or more {@code @Controller} + * instances. *

The minimum infrastructure required by the * {@link org.springframework.web.servlet.DispatcherServlet DispatcherServlet} * to serve requests with annotated controllers is created. Consider using @@ -187,8 +203,8 @@ public final class MockMvcTester { } /** - * Return a new {@link MockMvcTester} instance using the specified - * {@linkplain HttpMessageConverter message converters}. + * Return a new instance using the specified {@linkplain HttpMessageConverter + * message converters}. *

If none are specified, only basic assertions on the response body can * be performed. Consider registering a suitable JSON converter for asserting * against JSON data structures. @@ -200,8 +216,105 @@ public final class MockMvcTester { } /** - * Perform a request and return a {@link MvcTestResult result} that can be - * used with standard {@link org.assertj.core.api.Assertions AssertJ} assertions. + * Prepare an HTTP GET request. + *

The returned builder can be wrapped in {@code assertThat} to enable + * assertions on the result. For multi-statements assertions, use + * {@linkplain MockMvcRequestBuilder#exchange() exchange} to assign the + * result. + * @return a request builder for specifying the target URI + */ + public MockMvcRequestBuilder get() { + return method(HttpMethod.GET); + } + + /** + * Prepare an HTTP HEAD request. + *

The returned builder can be wrapped in {@code assertThat} to enable + * assertions on the result. For multi-statements assertions, use + * {@linkplain MockMvcRequestBuilder#exchange() exchange} to assign the + * result. + * @return a request builder for specifying the target URI + */ + public MockMvcRequestBuilder head() { + return method(HttpMethod.HEAD); + } + + /** + * Prepare an HTTP POST request. + *

The returned builder can be wrapped in {@code assertThat} to enable + * assertions on the result. For multi-statements assertions, use + * {@linkplain MockMvcRequestBuilder#exchange() exchange} to assign the + * result. + * @return a request builder for specifying the target URI + */ + public MockMvcRequestBuilder post() { + return method(HttpMethod.POST); + } + + /** + * Prepare an HTTP PUT request. + *

The returned builder can be wrapped in {@code assertThat} to enable + * assertions on the result. For multi-statements assertions, use + * {@linkplain MockMvcRequestBuilder#exchange() exchange} to assign the + * result. + * @return a request builder for specifying the target URI + */ + public MockMvcRequestBuilder put() { + return method(HttpMethod.PUT); + } + + /** + * Prepare an HTTP PATCH request. + *

The returned builder can be wrapped in {@code assertThat} to enable + * assertions on the result. For multi-statements assertions, use + * {@linkplain MockMvcRequestBuilder#exchange() exchange} to assign the + * result. + * @return a request builder for specifying the target URI + */ + public MockMvcRequestBuilder patch() { + return method(HttpMethod.PATCH); + } + + /** + * Prepare an HTTP DELETE request. + *

The returned builder can be wrapped in {@code assertThat} to enable + * assertions on the result. For multi-statements assertions, use + * {@linkplain MockMvcRequestBuilder#exchange() exchange} to assign the + * result. + * @return a request builder for specifying the target URI + */ + public MockMvcRequestBuilder delete() { + return method(HttpMethod.DELETE); + } + + /** + * Prepare an HTTP OPTIONS request. + *

The returned builder can be wrapped in {@code assertThat} to enable + * assertions on the result. For multi-statements assertions, use + * {@linkplain MockMvcRequestBuilder#exchange() exchange} to assign the + * result. + * @return a request builder for specifying the target URI + */ + public MockMvcRequestBuilder options() { + return method(HttpMethod.OPTIONS); + } + + /** + * Prepare a request for the specified {@code HttpMethod}. + *

The returned builder can be wrapped in {@code assertThat} to enable + * assertions on the result. For multi-statements assertions, use + * {@linkplain MockMvcRequestBuilder#exchange() exchange} to assign the + * result. + * @return a request builder for specifying the target URI + */ + public MockMvcRequestBuilder method(HttpMethod method) { + return new MockMvcRequestBuilder(method); + } + + /** + * Perform a request using {@link MockMvcRequestBuilders} and return a + * {@link MvcTestResult result} that can be used with standard + * {@link org.assertj.core.api.Assertions AssertJ} assertions. *

Use static methods of {@link MockMvcRequestBuilders} to prepare the * request, wrapping the invocation in {@code assertThat}. The following * asserts that a {@linkplain MockMvcRequestBuilders#get(URI) GET} request @@ -226,6 +339,8 @@ public final class MockMvcTester { * {@link org.springframework.test.web.servlet.request.MockMvcRequestBuilders} * @return an {@link MvcTestResult} to be wrapped in {@code assertThat} * @see MockMvc#perform(RequestBuilder) + * @see #get() + * @see #post() */ public MvcTestResult perform(RequestBuilder requestBuilder) { Object result = getMvcResultOrFailure(requestBuilder); @@ -259,4 +374,25 @@ public final class MockMvcTester { .findFirst().orElse(null); } + + /** + * A builder for {@link MockHttpServletRequest} that supports AssertJ. + */ + public final class MockMvcRequestBuilder extends AbstractMockHttpServletRequestBuilder + implements AssertProvider { + + private MockMvcRequestBuilder(HttpMethod httpMethod) { + super(httpMethod); + } + + public MvcTestResult exchange() { + return perform(this); + } + + @Override + public MvcTestResultAssert assertThat() { + return new MvcTestResultAssert(exchange(), MockMvcTester.this.jsonMessageConverter); + } + } + } diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterCompatibilityIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterCompatibilityIntegrationTests.java new file mode 100644 index 00000000000..712b725d90f --- /dev/null +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterCompatibilityIntegrationTests.java @@ -0,0 +1,94 @@ +/* + * Copyright 2002-2024 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.assertj; + +import org.junit.jupiter.api.Test; + +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.context.annotation.Import; +import org.springframework.http.MediaType; +import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; +import org.springframework.test.context.web.WebAppConfiguration; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.context.WebApplicationContext; +import org.springframework.web.servlet.config.annotation.EnableWebMvc; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +/** + * Integration tests for {@link MockMvcTester} that use the methods that + * integrate with {@link MockMvc} way of building the requests and + * asserting the responses. + * + * @author Stephane Nicoll + */ +@SpringJUnitConfig +@WebAppConfiguration +class MockMvcTesterCompatibilityIntegrationTests { + + private final MockMvcTester mvc; + + MockMvcTesterCompatibilityIntegrationTests(@Autowired WebApplicationContext wac) { + this.mvc = MockMvcTester.from(wac); + } + + @Test + void performGet() { + assertThat(this.mvc.perform(get("/greet"))).hasStatusOk(); + } + + @Test + void performGetWithInvalidMediaTypeAssertion() { + MvcTestResult result = this.mvc.perform(get("/greet")); + assertThatExceptionOfType(AssertionError.class) + .isThrownBy(() -> assertThat(result).hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON)) + .withMessageContaining("is compatible with 'application/json'"); + } + + @Test + void assertHttpStatusCode() { + assertThat(this.mvc.get().uri("/greet")).matches(status().isOk()); + } + + + @Configuration + @EnableWebMvc + @Import(TestController.class) + static class WebConfiguration { + } + + @RestController + static class TestController { + + @GetMapping(path = "/greet", produces = "text/plain") + String greet() { + return "hello"; + } + + @GetMapping(path = "/message", produces = MediaType.APPLICATION_JSON_VALUE) + String message() { + return "{\"message\": \"hello\"}"; + } + } + +} diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java index 90f68c1f553..6778de3fbd0 100644 --- a/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java +++ b/spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java @@ -43,7 +43,7 @@ import org.springframework.stereotype.Controller; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; import org.springframework.test.context.web.WebAppConfiguration; import org.springframework.test.web.Person; -import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder; +import org.springframework.test.web.servlet.ResultMatcher; import org.springframework.ui.Model; import org.springframework.validation.Errors; import org.springframework.web.bind.annotation.GetMapping; @@ -63,9 +63,8 @@ import static java.util.Map.entry; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.InstanceOfAssertFactories.map; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; -import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; /** * Integration tests for {@link MockMvcTester}. @@ -77,10 +76,10 @@ import static org.springframework.test.web.servlet.result.MockMvcResultMatchers. @WebAppConfiguration public class MockMvcTesterIntegrationTests { - private final MockMvcTester mockMvc; + private final MockMvcTester mvc; MockMvcTesterIntegrationTests(WebApplicationContext wac) { - this.mockMvc = MockMvcTester.from(wac); + this.mvc = MockMvcTester.from(wac); } @Nested @@ -88,24 +87,24 @@ public class MockMvcTesterIntegrationTests { @Test void hasAsyncStartedTrue() { - assertThat(perform(get("/callable").accept(MediaType.APPLICATION_JSON))) + assertThat(mvc.get().uri("/callable").accept(MediaType.APPLICATION_JSON)) .request().hasAsyncStarted(true); } @Test void hasAsyncStartedFalse() { - assertThat(perform(get("/greet"))).request().hasAsyncStarted(false); + assertThat(mvc.get().uri("/greet")).request().hasAsyncStarted(false); } @Test void attributes() { - assertThat(perform(get("/greet"))).request().attributes() + assertThat(mvc.get().uri("/greet")).request().attributes() .containsKey(DispatcherServlet.WEB_APPLICATION_CONTEXT_ATTRIBUTE); } @Test void sessionAttributes() { - assertThat(perform(get("/locale"))).request().sessionAttributes() + assertThat(mvc.get().uri("/locale")).request().sessionAttributes() .containsOnly(entry("locale", Locale.UK)); } } @@ -116,17 +115,17 @@ public class MockMvcTesterIntegrationTests { @Test void containsCookie() { Cookie cookie = new Cookie("test", "value"); - assertThat(performWithCookie(cookie, get("/greet"))).cookies().containsCookie("test"); + assertThat(withCookie(cookie).get().uri("/greet")).cookies().containsCookie("test"); } @Test void hasValue() { Cookie cookie = new Cookie("test", "value"); - assertThat(performWithCookie(cookie, get("/greet"))).cookies().hasValue("test", "value"); + assertThat(withCookie(cookie).get().uri("/greet")).cookies().hasValue("test", "value"); } - private MvcTestResult performWithCookie(Cookie cookie, MockHttpServletRequestBuilder request) { - MockMvcTester mockMvc = MockMvcTester.of(List.of(new TestController()), builder -> builder.addInterceptors( + private MockMvcTester withCookie(Cookie cookie) { + return MockMvcTester.of(List.of(new TestController()), builder -> builder.addInterceptors( new HandlerInterceptor() { @Override public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) { @@ -134,7 +133,6 @@ public class MockMvcTesterIntegrationTests { return true; } }).build()); - return mockMvc.perform(request); } } @@ -143,12 +141,12 @@ public class MockMvcTesterIntegrationTests { @Test void statusOk() { - assertThat(perform(get("/greet"))).hasStatusOk(); + assertThat(mvc.get().uri("/greet")).hasStatusOk(); } @Test void statusSeries() { - assertThat(perform(get("/greet"))).hasStatus2xxSuccessful(); + assertThat(mvc.get().uri("/greet")).hasStatus2xxSuccessful(); } } @@ -158,13 +156,13 @@ public class MockMvcTesterIntegrationTests { @Test void shouldAssertHeader() { - assertThat(perform(get("/greet"))) + assertThat(mvc.get().uri("/greet")) .hasHeader("Content-Type", "text/plain;charset=ISO-8859-1"); } @Test void shouldAssertHeaderWithCallback() { - assertThat(perform(get("/greet"))).headers().satisfies(textContent("ISO-8859-1")); + assertThat(mvc.get().uri("/greet")).headers().satisfies(textContent("ISO-8859-1")); } private Consumer textContent(String charset) { @@ -179,33 +177,33 @@ public class MockMvcTesterIntegrationTests { @Test void hasViewName() { - assertThat(perform(get("/persons/{0}", "Andy"))).hasViewName("persons/index"); + assertThat(mvc.get().uri("/persons/{0}", "Andy")).hasViewName("persons/index"); } @Test void viewNameWithCustomAssertion() { - assertThat(perform(get("/persons/{0}", "Andy"))).viewName().startsWith("persons"); + assertThat(mvc.get().uri("/persons/{0}", "Andy")).viewName().startsWith("persons"); } @Test void containsAttributes() { - assertThat(perform(post("/persons").param("name", "Andy"))).model() + assertThat(mvc.post().uri("/persons").param("name", "Andy")).model() .containsOnlyKeys("name").containsEntry("name", "Andy"); } @Test void hasErrors() { - assertThat(perform(post("/persons"))).model().hasErrors(); + assertThat(mvc.post().uri("/persons")).model().hasErrors(); } @Test void hasAttributeErrors() { - assertThat(perform(post("/persons"))).model().hasAttributeErrors("person"); + assertThat(mvc.post().uri("/persons")).model().hasAttributeErrors("person"); } @Test void hasAttributeErrorsCount() { - assertThat(perform(post("/persons"))).model().extractingBindingResult("person").hasErrorsCount(1); + assertThat(mvc.post().uri("/persons")).model().extractingBindingResult("person").hasErrorsCount(1); } } @@ -215,7 +213,7 @@ public class MockMvcTesterIntegrationTests { @Test void containsAttributes() { - assertThat(perform(post("/persons").param("name", "Andy"))).flash() + assertThat(mvc.post().uri("/persons").param("name", "Andy")).flash() .containsOnlyKeys("message").hasEntrySatisfying("message", value -> assertThat(value).isInstanceOfSatisfying(String.class, stringValue -> assertThat(stringValue).startsWith("success"))); @@ -227,31 +225,31 @@ public class MockMvcTesterIntegrationTests { @Test void asyncResult() { - assertThat(perform(get("/callable").accept(MediaType.APPLICATION_JSON))) + assertThat(mvc.get().uri("/callable").accept(MediaType.APPLICATION_JSON)) .asyncResult().asInstanceOf(map(String.class, Object.class)) .containsOnly(entry("key", "value")); } @Test void stringContent() { - assertThat(perform(get("/greet"))).body().asString().isEqualTo("hello"); + assertThat(mvc.get().uri("/greet")).body().asString().isEqualTo("hello"); } @Test void jsonPathContent() { - assertThat(perform(get("/message"))).bodyJson() + assertThat(mvc.get().uri("/message")).bodyJson() .extractingPath("$.message").asString().isEqualTo("hello"); } @Test void jsonContentCanLoadResourceFromClasspath() { - assertThat(perform(get("/message"))).bodyJson().isLenientlyEqualTo( + assertThat(mvc.get().uri("/message")).bodyJson().isLenientlyEqualTo( new ClassPathResource("message.json", MockMvcTesterIntegrationTests.class)); } @Test void jsonContentUsingResourceLoaderClass() { - assertThat(perform(get("/message"))).bodyJson().withResourceLoadClass(MockMvcTesterIntegrationTests.class) + assertThat(mvc.get().uri("/message")).bodyJson().withResourceLoadClass(MockMvcTesterIntegrationTests.class) .isLenientlyEqualTo("message.json"); } @@ -262,22 +260,22 @@ public class MockMvcTesterIntegrationTests { @Test void handlerOn404() { - assertThat(perform(get("/unknown-resource"))).handler().isNull(); + assertThat(mvc.get().uri("/unknown-resource")).handler().isNull(); } @Test void hasType() { - assertThat(perform(get("/greet"))).handler().hasType(TestController.class); + assertThat(mvc.get().uri("/greet")).handler().hasType(TestController.class); } @Test void isMethodHandler() { - assertThat(perform(get("/greet"))).handler().isMethodHandler(); + assertThat(mvc.get().uri("/greet")).handler().isMethodHandler(); } @Test void isInvokedOn() { - assertThat(perform(get("/callable"))).handler() + assertThat(mvc.get().uri("/callable")).handler() .isInvokedOn(AsyncController.class, AsyncController::getCallable); } @@ -288,31 +286,31 @@ public class MockMvcTesterIntegrationTests { @Test void doesNotHaveUnresolvedException() { - assertThat(perform(get("/greet"))).doesNotHaveUnresolvedException(); + assertThat(mvc.get().uri("/greet")).doesNotHaveUnresolvedException(); } @Test void hasUnresolvedException() { - assertThat(perform(get("/error/1"))).hasUnresolvedException(); + assertThat(mvc.get().uri("/error/1")).hasUnresolvedException(); } @Test void doesNotHaveUnresolvedExceptionWithUnresolvedException() { assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> assertThat(perform(get("/error/1"))).doesNotHaveUnresolvedException()) + .isThrownBy(() -> assertThat(mvc.get().uri("/error/1")).doesNotHaveUnresolvedException()) .withMessage("Expected request to succeed, but it failed"); } @Test void hasUnresolvedExceptionWithoutUnresolvedException() { assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> assertThat(perform(get("/greet"))).hasUnresolvedException()) + .isThrownBy(() -> assertThat(mvc.get().uri("/greet")).hasUnresolvedException()) .withMessage("Expected request to fail, but it succeeded"); } @Test void unresolvedExceptionWithFailedRequest() { - assertThat(perform(get("/error/1"))).unresolvedException() + assertThat(mvc.get().uri("/error/1")).unresolvedException() .isInstanceOf(ServletException.class) .cause().isInstanceOf(IllegalStateException.class).hasMessage("Expected"); } @@ -320,7 +318,7 @@ public class MockMvcTesterIntegrationTests { @Test void unresolvedExceptionWithSuccessfulRequest() { assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> assertThat(perform(get("/greet"))).unresolvedException()) + .isThrownBy(() -> assertThat(mvc.get().uri("/greet")).unresolvedException()) .withMessage("Expected request to fail, but it succeeded"); } @@ -406,7 +404,7 @@ public class MockMvcTesterIntegrationTests { private void testAssertionFailureWithUnresolvableException(Consumer assertions) { - MvcTestResult result = perform(get("/error/1")); + MvcTestResult result = mvc.get().uri("/error/1").exchange(); assertThatExceptionOfType(AssertionError.class) .isThrownBy(() -> assertions.accept(result)) .withMessageContainingAll("Request failed unexpectedly:", @@ -418,49 +416,49 @@ public class MockMvcTesterIntegrationTests { @Test void hasForwardUrl() { - assertThat(perform(get("/persons/John"))).hasForwardedUrl("persons/index"); + assertThat(mvc.get().uri("/persons/John")).hasForwardedUrl("persons/index"); } @Test void hasRedirectUrl() { - assertThat(perform(post("/persons").param("name", "Andy"))).hasStatus(HttpStatus.FOUND) + assertThat(mvc.post().uri("/persons").param("name", "Andy")).hasStatus(HttpStatus.FOUND) .hasRedirectedUrl("/persons/Andy"); } @Test void satisfiesAllowsAdditionalAssertions() { - assertThat(this.mockMvc.perform(get("/greet"))).satisfies(result -> { + assertThat(mvc.get().uri("/greet")).satisfies(result -> { assertThat(result).isInstanceOf(MvcTestResult.class); assertThat(result).hasStatusOk(); }); } @Test - void resultMatcherCanBeReused() { - assertThat(this.mockMvc.perform(get("/greet"))).matches(status().isOk()); + void resultMatcherCanBeReused() throws Exception { + MvcTestResult result = mvc.get().uri("/greet").exchange(); + ResultMatcher matcher = mock(ResultMatcher.class); + assertThat(result).matches(matcher); + verify(matcher).match(result.getMvcResult()); } @Test void resultMatcherFailsWithDedicatedException() { + ResultMatcher matcher = result -> assertThat(result.getResponse().getStatus()) + .isEqualTo(HttpStatus.NOT_FOUND.value()); assertThatExceptionOfType(AssertionError.class) - .isThrownBy(() -> assertThat(this.mockMvc.perform(get("/greet"))) - .matches(status().isNotFound())) - .withMessageContaining("Status expected:<404> but was:<200>"); + .isThrownBy(() -> assertThat(mvc.get().uri("/greet")) + .matches(matcher)) + .withMessageContaining("expected: 404").withMessageContaining(" but was: 200"); } @Test void shouldApplyResultHandler() { // Spring RESTDocs example AtomicBoolean applied = new AtomicBoolean(); - assertThat(this.mockMvc.perform(get("/greet"))).apply(result -> applied.set(true)); + assertThat(mvc.get().uri("/greet")).apply(result -> applied.set(true)); assertThat(applied).isTrue(); } - private MvcTestResult perform(MockHttpServletRequestBuilder builder) { - return this.mockMvc.perform(builder); - } - - @Configuration @EnableWebMvc @Import({ TestController.class, PersonController.class, AsyncController.class,