Add explicit support for asynchronous requests in MockMvcTester

This commit makes asynchronous requests first class by providing a
MvcTestResult that represents the final, completed, state of a request
by default. Previously, the result was an intermediate step that may
require an ASYNC dispatch to be fully usable. Now it is de facto
immutable.

To make things a bit more explicit, an `.exchange(Duration)` method has
been added to provide a dedicated time to wait. The default applies a
default timeout that is consistent with what MVcResult#getAsyncResult
does.

Given that we apply the ASYNC dispatch automatically, the intermediate
response is no longer available by default. As a result, the asyncResult
is not available for assertions.

As always, it is possible to use plain MockMvc by using the `perform`
method that takes the regular RequestBuilder as an input. When this
method is invoked, no asynchronous handling is done.

Closes gh-33040
This commit is contained in:
Stéphane Nicoll
2024-06-17 07:55:53 +02:00
parent a1b0099795
commit a7503e7200
5 changed files with 359 additions and 134 deletions

View File

@@ -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}.
* <p>For simple assertions, you can wrap this builder in
* {@code assertThat} rather than calling this method explicitly:
* <pre><code class="java">
* // These two examples are equivalent
* assertThat(mvc.get().uri("/greet")).hasStatusOk();
* assertThat(mvc.get().uri("/greet").exchange()).hasStatusOk();
* </code></pre>
* @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}.
* <p>For simple assertions, you can wrap this builder in
* {@code assertThat} rather than calling this method explicitly:
* <pre><code class="java">
* // These two examples are equivalent
* assertThat(mvc.get().uri("/greet")).hasStatusOk();
* assertThat(mvc.get().uri("/greet").exchange()).hasStatusOk();
* </code></pre>
* @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

View File

@@ -38,6 +38,10 @@ import org.springframework.test.web.servlet.MvcResult;
* {@linkplain #getMvcResult() result} will fail with an exception.</li>
* </ol>
*
* <p>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<MvcTestResultAssert> {
return getMvcResult().getResponse();
}
/**
* Return the exception that was thrown unexpectedly while processing the
* request, if any.

View File

@@ -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<M
return new MapAssert<>(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<Object> asyncResult() {
request().hasAsyncStarted(true);
return Assertions.assertThat(getMvcResult().getAsyncResult()).as("Async result");
}
/**
* Print {@link MvcResult} details to {@code System.out}.
* <p>You must call it <b>before</b> calling the assertion otherwise it is ignored

View File

@@ -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<Map<String, String>> 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

View File

@@ -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");
}
}