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 a1ce85d696..61b65b2bb3 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
@@ -17,12 +17,14 @@
package org.springframework.test.web.servlet.assertj;
import java.net.URI;
+import java.time.Duration;
import java.util.Arrays;
import java.util.Collection;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.StreamSupport;
+import jakarta.servlet.DispatcherType;
import org.assertj.core.api.AssertProvider;
import org.springframework.http.HttpMethod;
@@ -384,6 +386,34 @@ public final class MockMvcTester {
.findFirst().orElse(null);
}
+ /**
+ * Execute the request using the specified {@link RequestBuilder}. If the
+ * request is processing asynchronously, wait at most the given
+ * {@code timeToWait} duration. If not specified, then fall back on the
+ * timeout value associated with the async request, see
+ * {@link org.springframework.mock.web.MockAsyncContext#setTimeout}.
+ */
+ MvcTestResult exchange(RequestBuilder requestBuilder, @Nullable Duration timeToWait) {
+ MvcTestResult result = perform(requestBuilder);
+ if (result.getUnresolvedException() == null) {
+ if (result.getRequest().isAsyncStarted()) {
+ // Wait for async result before dispatching
+ long waitMs = (timeToWait != null ? timeToWait.toMillis() : -1);
+ result.getMvcResult().getAsyncResult(waitMs);
+
+ // Perform ASYNC dispatch
+ RequestBuilder dispatchRequest = servletContext -> {
+ MockHttpServletRequest request = result.getMvcResult().getRequest();
+ request.setDispatcherType(DispatcherType.ASYNC);
+ request.setAsyncStarted(false);
+ return request;
+ };
+ return perform(dispatchRequest);
+ }
+ }
+ return result;
+ }
+
/**
* A builder for {@link MockHttpServletRequest} that supports AssertJ.
@@ -407,8 +437,31 @@ public final class MockMvcTester {
return new MockMultipartMvcRequestBuilder(this);
}
+ /**
+ * Execute the request. If the request is processing asynchronously,
+ * wait at most the given timeout value associated with the async request,
+ * see {@link org.springframework.mock.web.MockAsyncContext#setTimeout}.
+ *
For simple assertions, you can wrap this builder in
+ * {@code assertThat} rather than calling this method explicitly:
+ *
+ * // These two examples are equivalent
+ * assertThat(mvc.get().uri("/greet")).hasStatusOk();
+ * assertThat(mvc.get().uri("/greet").exchange()).hasStatusOk();
+ *
+ * @see #exchange(Duration) to customize the timeout for async requests
+ */
public MvcTestResult exchange() {
- return perform(this);
+ return MockMvcTester.this.exchange(this, null);
+ }
+
+ /**
+ * Execute the request and wait at most the given {@code timeToWait}
+ * duration for the asynchronous request to complete. If the request
+ * is not asynchronous, the {@code timeToWait} is ignored.
+ * @see #exchange()
+ */
+ public MvcTestResult exchange(Duration timeToWait) {
+ return MockMvcTester.this.exchange(this, timeToWait);
}
@Override
@@ -429,8 +482,31 @@ public final class MockMvcTester {
merge(currentBuilder);
}
+ /**
+ * Execute the request. If the request is processing asynchronously,
+ * wait at most the given timeout value associated with the async request,
+ * see {@link org.springframework.mock.web.MockAsyncContext#setTimeout}.
+ * For simple assertions, you can wrap this builder in
+ * {@code assertThat} rather than calling this method explicitly:
+ *
+ * // These two examples are equivalent
+ * assertThat(mvc.get().uri("/greet")).hasStatusOk();
+ * assertThat(mvc.get().uri("/greet").exchange()).hasStatusOk();
+ *
+ * @see #exchange(Duration) to customize the timeout for async requests
+ */
public MvcTestResult exchange() {
- return perform(this);
+ return MockMvcTester.this.exchange(this, null);
+ }
+
+ /**
+ * Execute the request and wait at most the given {@code timeToWait}
+ * duration for the asynchronous request to complete. If the request
+ * is not asynchronous, the {@code timeToWait} is ignored.
+ * @see #exchange()
+ */
+ public MvcTestResult exchange(Duration timeToWait) {
+ return MockMvcTester.this.exchange(this, timeToWait);
}
@Override
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResult.java b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResult.java
index 805953567e..20b6e942db 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResult.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResult.java
@@ -38,6 +38,10 @@ import org.springframework.test.web.servlet.MvcResult;
* {@linkplain #getMvcResult() result} will fail with an exception.
*
*
+ * If the request was asynchronous, it is fully resolved at this point and
+ * regular assertions can be applied without having to wait for the completion
+ * of the response.
+ *
* @author Stephane Nicoll
* @author Brian Clozel
* @since 6.2
@@ -69,7 +73,6 @@ public interface MvcTestResult extends AssertProvider {
return getMvcResult().getResponse();
}
-
/**
* Return the exception that was thrown unexpectedly while processing the
* request, if any.
diff --git a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResultAssert.java b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResultAssert.java
index 614e32da2f..53ccf51367 100644
--- a/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResultAssert.java
+++ b/spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MvcTestResultAssert.java
@@ -28,7 +28,6 @@ import org.assertj.core.api.AbstractStringAssert;
import org.assertj.core.api.AbstractThrowableAssert;
import org.assertj.core.api.Assertions;
import org.assertj.core.api.MapAssert;
-import org.assertj.core.api.ObjectAssert;
import org.assertj.core.error.BasicErrorMessageFactory;
import org.assertj.core.internal.Failures;
@@ -128,17 +127,6 @@ public class MvcTestResultAssert extends AbstractMockHttpServletResponseAssert(getMvcResult().getFlashMap());
}
- /**
- * Verify that {@linkplain AbstractHttpServletRequestAssert#hasAsyncStarted(boolean)
- * asynchronous processing has started} and return a new
- * {@linkplain ObjectAssert assertion} object that uses the asynchronous
- * result as the object to test.
- */
- public ObjectAssert asyncResult() {
- request().hasAsyncStarted(true);
- return Assertions.assertThat(getMvcResult().getAsyncResult()).as("Async result");
- }
-
/**
* Print {@link MvcResult} details to {@code System.out}.
* You must call it before calling the assertion otherwise it is ignored
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 e55d2a0a21..aebfac1c74 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
@@ -20,6 +20,7 @@ import java.io.ByteArrayOutputStream;
import java.io.PrintStream;
import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
+import java.time.Duration;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
@@ -36,6 +37,7 @@ import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.Part;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
+import org.assertj.core.api.InstanceOfAssertFactories;
import org.junit.jupiter.api.AfterEach;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
@@ -53,6 +55,7 @@ import org.springframework.stereotype.Controller;
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
import org.springframework.test.web.Person;
import org.springframework.test.web.servlet.ResultMatcher;
+import org.springframework.test.web.servlet.assertj.MockMvcTester.MockMvcRequestBuilder;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.GetMapping;
@@ -73,14 +76,16 @@ import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.HandlerInterceptor;
import org.springframework.web.servlet.ModelAndView;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
+import org.springframework.web.servlet.mvc.method.annotation.StreamingResponseBody;
import org.springframework.web.servlet.mvc.support.RedirectAttributes;
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.assertj.core.api.Assertions.assertThatIllegalStateException;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
/**
* Integration tests for {@link MockMvcTester}.
@@ -97,12 +102,60 @@ public class MockMvcTesterIntegrationTests {
this.mvc = MockMvcTester.from(wac);
}
+ @Nested
+ class PerformTests {
+
+ @Test
+ void syncRequestWithDefaultExchange() {
+ assertThat(mvc.get().uri("/greet")).hasStatusOk();
+ }
+
+ @Test
+ void asyncRequestWithDefaultExchange() {
+ assertThat(mvc.get().uri("/streaming").param("timeToWait", "100")).hasStatusOk()
+ .hasBodyTextEqualTo("name=Joe&someBoolean=true");
+ }
+
+ @Test
+ void syncRequestWithExplicitExchange() {
+ assertThat(mvc.get().uri("/greet").exchange()).hasStatusOk();
+ }
+
+ @Test
+ void asyncRequestWithExplicitExchange() {
+ assertThat(mvc.get().uri("/streaming").param("timeToWait", "100").exchange())
+ .hasStatusOk().hasBodyTextEqualTo("name=Joe&someBoolean=true");
+ }
+
+ @Test
+ void syncRequestWithExplicitExchangeIgnoresDuration() {
+ Duration timeToWait = mock(Duration.class);
+ assertThat(mvc.get().uri("/greet").exchange(timeToWait)).hasStatusOk();
+ verifyNoInteractions(timeToWait);
+ }
+
+ @Test
+ void asyncRequestWithExplicitExchangeAndEnoughTimeToWait() {
+ assertThat(mvc.get().uri("/streaming").param("timeToWait", "100").exchange(Duration.ofMillis(200)))
+ .hasStatusOk().hasBodyTextEqualTo("name=Joe&someBoolean=true");
+ }
+
+ @Test
+ void asyncRequestWithExplicitExchangeAndNotEnoughTimeToWait() {
+ MockMvcRequestBuilder builder = mvc.get().uri("/streaming").param("timeToWait", "500");
+ assertThatIllegalStateException()
+ .isThrownBy(() -> builder.exchange(Duration.ofMillis(100)))
+ .withMessageContaining("was not set during the specified timeToWait=100");
+ }
+ }
+
@Nested
class RequestTests {
@Test
void hasAsyncStartedTrue() {
- assertThat(mvc.get().uri("/callable").accept(MediaType.APPLICATION_JSON))
+ // Need #perform as the regular exchange waits for async completion automatically
+ assertThat(mvc.perform(mvc.get().uri("/callable").accept(MediaType.APPLICATION_JSON)))
.request().hasAsyncStarted(true);
}
@@ -272,8 +325,10 @@ public class MockMvcTesterIntegrationTests {
@Test
void asyncResult() {
- assertThat(mvc.get().uri("/callable").accept(MediaType.APPLICATION_JSON))
- .asyncResult().asInstanceOf(map(String.class, Object.class))
+ // Need #perform as the regular exchange waits for async completion automatically
+ MvcTestResult result = mvc.perform(mvc.get().uri("/callable").accept(MediaType.APPLICATION_JSON));
+ assertThat(result.getMvcResult().getAsyncResult())
+ .asInstanceOf(InstanceOfAssertFactories.map(String.class, Object.class))
.containsOnly(entry("key", "value"));
}
@@ -441,12 +496,6 @@ public class MockMvcTesterIntegrationTests {
result -> assertThat(result).apply(mvcResult -> {}));
}
- @Test
- void assertAsyncResultWithUnresolvedException() {
- testAssertionFailureWithUnresolvableException(
- result -> assertThat(result).asyncResult());
- }
-
@Test
void assertContentTypeWithUnresolvedException() {
testAssertionFailureWithUnresolvableException(
@@ -617,6 +666,21 @@ public class MockMvcTesterIntegrationTests {
public Callable> getCallable() {
return () -> Collections.singletonMap("key", "value");
}
+
+ @GetMapping("/streaming")
+ StreamingResponseBody streaming(@RequestParam long timeToWait) {
+ return out -> {
+ PrintStream stream = new PrintStream(out, true, StandardCharsets.UTF_8);
+ stream.print("name=Joe");
+ try {
+ Thread.sleep(timeToWait);
+ stream.print("&someBoolean=true");
+ }
+ catch (InterruptedException e) {
+ /* no-op */
+ }
+ };
+ }
}
@Controller
diff --git a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java
index ec82a970b2..80155a8773 100644
--- a/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java
+++ b/spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2023 the original author or authors.
+ * 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.
@@ -22,6 +22,7 @@ import java.time.Duration;
import java.util.concurrent.Callable;
import java.util.concurrent.CompletableFuture;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import reactor.core.publisher.Mono;
@@ -31,6 +32,9 @@ import org.springframework.http.ResponseEntity;
import org.springframework.test.web.Person;
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.assertj.MockMvcTester;
+import org.springframework.test.web.servlet.assertj.MvcTestResult;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseStatus;
@@ -58,132 +62,222 @@ import static org.springframework.test.web.servlet.setup.MockMvcBuilders.standal
*/
class AsyncTests {
- private final AsyncController asyncController = new AsyncController();
+ @Nested
+ class MockMvcTests {
- private final MockMvc mockMvc = standaloneSetup(this.asyncController).build();
+ private final MockMvc mockMvc = standaloneSetup(new AsyncController()).build();
+ @Test
+ void callable() throws Exception {
+ MvcResult mvcResult = this.mockMvc.perform(get("/1").param("callable", "true"))
+ .andExpect(request().asyncStarted())
+ .andExpect(request().asyncResult(equalTo(new Person("Joe"))))
+ .andExpect(request().asyncResult(new Person("Joe")))
+ .andReturn();
- @Test
- void callable() throws Exception {
- MvcResult mvcResult = this.mockMvc.perform(get("/1").param("callable", "true"))
- .andExpect(request().asyncStarted())
- .andExpect(request().asyncResult(equalTo(new Person("Joe"))))
- .andExpect(request().asyncResult(new Person("Joe")))
- .andReturn();
+ this.mockMvc.perform(asyncDispatch(mvcResult))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
+ }
- this.mockMvc.perform(asyncDispatch(mvcResult))
- .andExpect(status().isOk())
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
+ @Test
+ void streaming() throws Exception {
+ this.mockMvc.perform(get("/1").param("streaming", "true"))
+ .andExpect(request().asyncStarted())
+ .andDo(MvcResult::getAsyncResult) // fetch async result similar to "asyncDispatch" builder
+ .andExpect(status().isOk())
+ .andExpect(content().string("name=Joe"));
+ }
+
+ @Test
+ void streamingSlow() throws Exception {
+ this.mockMvc.perform(get("/1").param("streamingSlow", "true"))
+ .andExpect(request().asyncStarted())
+ .andDo(MvcResult::getAsyncResult)
+ .andExpect(status().isOk())
+ .andExpect(content().string("name=Joe&someBoolean=true"));
+ }
+
+ @Test
+ void streamingJson() throws Exception {
+ this.mockMvc.perform(get("/1").param("streamingJson", "true"))
+ .andExpect(request().asyncStarted())
+ .andDo(MvcResult::getAsyncResult)
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.5}"));
+ }
+
+ @Test
+ void deferredResult() throws Exception {
+ MvcResult mvcResult = this.mockMvc.perform(get("/1").param("deferredResult", "true"))
+ .andExpect(request().asyncStarted())
+ .andReturn();
+
+ this.mockMvc.perform(asyncDispatch(mvcResult))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
+ }
+
+ @Test
+ void deferredResultWithImmediateValue() throws Exception {
+ MvcResult mvcResult = this.mockMvc.perform(get("/1").param("deferredResultWithImmediateValue", "true"))
+ .andExpect(request().asyncStarted())
+ .andExpect(request().asyncResult(new Person("Joe")))
+ .andReturn();
+
+ this.mockMvc.perform(asyncDispatch(mvcResult))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
+ }
+
+ @Test // SPR-13079
+ void deferredResultWithDelayedError() throws Exception {
+ MvcResult mvcResult = this.mockMvc.perform(get("/1").param("deferredResultWithDelayedError", "true"))
+ .andExpect(request().asyncStarted())
+ .andReturn();
+
+ this.mockMvc.perform(asyncDispatch(mvcResult))
+ .andExpect(status().is5xxServerError())
+ .andExpect(content().string("Delayed Error"));
+ }
+
+ @Test
+ void listenableFuture() throws Exception {
+ MvcResult mvcResult = this.mockMvc.perform(get("/1").param("listenableFuture", "true"))
+ .andExpect(request().asyncStarted())
+ .andReturn();
+
+ this.mockMvc.perform(asyncDispatch(mvcResult))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
+ }
+
+ @Test // SPR-12597
+ void completableFutureWithImmediateValue() throws Exception {
+ MvcResult mvcResult = this.mockMvc.perform(get("/1").param("completableFutureWithImmediateValue", "true"))
+ .andExpect(request().asyncStarted())
+ .andReturn();
+
+ this.mockMvc.perform(asyncDispatch(mvcResult))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
+ }
+
+ @Test // SPR-12735
+ void printAsyncResult() throws Exception {
+ StringWriter writer = new StringWriter();
+
+ MvcResult mvcResult = this.mockMvc.perform(get("/1").param("deferredResult", "true"))
+ .andDo(print(writer))
+ .andExpect(request().asyncStarted())
+ .andReturn();
+
+ assertThat(writer.toString()).contains("Async started = true");
+ writer = new StringWriter();
+
+ this.mockMvc.perform(asyncDispatch(mvcResult))
+ .andDo(print(writer))
+ .andExpect(status().isOk())
+ .andExpect(content().contentType(MediaType.APPLICATION_JSON))
+ .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
+
+ assertThat(writer.toString()).contains("Async started = false");
+ }
}
- @Test
- void streaming() throws Exception {
- this.mockMvc.perform(get("/1").param("streaming", "true"))
- .andExpect(request().asyncStarted())
- .andDo(MvcResult::getAsyncResult) // fetch async result similar to "asyncDispatch" builder
- .andExpect(status().isOk())
- .andExpect(content().string("name=Joe"));
- }
+ @Nested
+ class MockMvcTesterTests {
- @Test
- void streamingSlow() throws Exception {
- this.mockMvc.perform(get("/1").param("streamingSlow", "true"))
- .andExpect(request().asyncStarted())
- .andDo(MvcResult::getAsyncResult)
- .andExpect(status().isOk())
- .andExpect(content().string("name=Joe&someBoolean=true"));
- }
+ private final MockMvcTester mockMvc = MockMvcTester.of(new AsyncController());
- @Test
- void streamingJson() throws Exception {
- this.mockMvc.perform(get("/1").param("streamingJson", "true"))
- .andExpect(request().asyncStarted())
- .andDo(MvcResult::getAsyncResult)
- .andExpect(status().isOk())
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.5}"));
- }
+ @Test
+ void callable() {
+ assertThat(mockMvc.get().uri("/1").param("callable", "true"))
+ .hasStatusOk()
+ .hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON)
+ .hasBodyTextEqualTo("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
+ }
- @Test
- void deferredResult() throws Exception {
- MvcResult mvcResult = this.mockMvc.perform(get("/1").param("deferredResult", "true"))
- .andExpect(request().asyncStarted())
- .andReturn();
+ @Test
+ void streaming() {
+ assertThat(this.mockMvc.get().uri("/1").param("streaming", "true"))
+ .hasStatusOk().hasBodyTextEqualTo("name=Joe");
+ }
- this.mockMvc.perform(asyncDispatch(mvcResult))
- .andExpect(status().isOk())
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
- }
+ @Test
+ void streamingSlow() {
+ assertThat(this.mockMvc.get().uri("/1").param("streamingSlow", "true"))
+ .hasStatusOk().hasBodyTextEqualTo("name=Joe&someBoolean=true");
+ }
- @Test
- void deferredResultWithImmediateValue() throws Exception {
- MvcResult mvcResult = this.mockMvc.perform(get("/1").param("deferredResultWithImmediateValue", "true"))
- .andExpect(request().asyncStarted())
- .andExpect(request().asyncResult(new Person("Joe")))
- .andReturn();
+ @Test
+ void streamingJson() {
+ assertThat(this.mockMvc.get().uri("/1").param("streamingJson", "true"))
+ .hasStatusOk()
+ .hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON)
+ .hasBodyTextEqualTo("{\"name\":\"Joe\",\"someDouble\":0.5}");
+ }
- this.mockMvc.perform(asyncDispatch(mvcResult))
- .andExpect(status().isOk())
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
- }
+ @Test
+ void deferredResult() {
+ assertThat(this.mockMvc.get().uri("/1").param("deferredResult", "true"))
+ .hasStatusOk()
+ .hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON)
+ .hasBodyTextEqualTo("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
+ }
- @Test // SPR-13079
- void deferredResultWithDelayedError() throws Exception {
- MvcResult mvcResult = this.mockMvc.perform(get("/1").param("deferredResultWithDelayedError", "true"))
- .andExpect(request().asyncStarted())
- .andReturn();
+ @Test
+ void deferredResultWithImmediateValue() {
+ assertThat(this.mockMvc.get().uri("/1").param("deferredResultWithImmediateValue", "true"))
+ .hasStatusOk()
+ .hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON)
+ .hasBodyTextEqualTo("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
+ }
- this.mockMvc.perform(asyncDispatch(mvcResult))
- .andExpect(status().is5xxServerError())
- .andExpect(content().string("Delayed Error"));
- }
+ @Test // SPR-13079
+ void deferredResultWithDelayedError() {
+ assertThat(this.mockMvc.get().uri("/1").param("deferredResultWithDelayedError", "true"))
+ .hasStatus5xxServerError().hasBodyTextEqualTo("Delayed Error");
+ }
- @Test
- void listenableFuture() throws Exception {
- MvcResult mvcResult = this.mockMvc.perform(get("/1").param("listenableFuture", "true"))
- .andExpect(request().asyncStarted())
- .andReturn();
+ @Test
+ void listenableFuture() {
+ assertThat(this.mockMvc.get().uri("/1").param("listenableFuture", "true"))
+ .hasStatusOk().hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON)
+ .hasBodyTextEqualTo("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
+ }
- this.mockMvc.perform(asyncDispatch(mvcResult))
- .andExpect(status().isOk())
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
- }
+ @Test // SPR-12597
+ void completableFutureWithImmediateValue() {
+ assertThat(this.mockMvc.get().uri("/1").param("completableFutureWithImmediateValue", "true"))
+ .hasStatusOk()
+ .hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON)
+ .hasBodyTextEqualTo("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
+ }
- @Test // SPR-12597
- void completableFutureWithImmediateValue() throws Exception {
- MvcResult mvcResult = this.mockMvc.perform(get("/1").param("completableFutureWithImmediateValue", "true"))
- .andExpect(request().asyncStarted())
- .andReturn();
+ @Test // SPR-12735
+ void printAsyncResult() {
+ StringWriter asyncWriter = new StringWriter();
- this.mockMvc.perform(asyncDispatch(mvcResult))
- .andExpect(status().isOk())
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
- }
-
- @Test // SPR-12735
- void printAsyncResult() throws Exception {
- StringWriter writer = new StringWriter();
-
- MvcResult mvcResult = this.mockMvc.perform(get("/1").param("deferredResult", "true"))
- .andDo(print(writer))
- .andExpect(request().asyncStarted())
- .andReturn();
-
- assertThat(writer.toString()).contains("Async started = true");
- writer = new StringWriter();
-
- this.mockMvc.perform(asyncDispatch(mvcResult))
- .andDo(print(writer))
- .andExpect(status().isOk())
- .andExpect(content().contentType(MediaType.APPLICATION_JSON))
- .andExpect(content().string("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}"));
-
- assertThat(writer.toString()).contains("Async started = false");
+ // Use #perform to not complete asynchronous request automatically
+ RequestBuilder requestBuilder = this.mockMvc.get().uri("/1").param("deferredResult", "true");
+ MvcTestResult result = this.mockMvc.perform(requestBuilder);
+ assertThat(result).debug(asyncWriter).request().hasAsyncStarted(true);
+ assertThat(asyncWriter.toString()).contains("Async started = true");
+ asyncWriter = new StringWriter(); // Reset
+ assertThat(this.mockMvc.perform(asyncDispatch(result.getMvcResult())))
+ .debug(asyncWriter)
+ .hasStatusOk()
+ .hasContentTypeCompatibleWith(MediaType.APPLICATION_JSON)
+ .hasBodyTextEqualTo("{\"name\":\"Joe\",\"someDouble\":0.0,\"someBoolean\":false}");
+ assertThat(asyncWriter.toString()).contains("Async started = false");
+ }
}