Improve support of async request in MockMvcTester

This commit improves the handling of asynchronous requests by offering
a way to opt-in for the raw async result. This provides first class
support for asserting a request that might still be in process as well
as the asyncResult, if necessary.

See gh-33040
This commit is contained in:
Stéphane Nicoll
2024-06-18 13:35:33 +02:00
parent a7503e7200
commit 24bbc6d80d
3 changed files with 80 additions and 10 deletions

View File

@@ -55,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.MockMultipartMvcRequestBuilder;
import org.springframework.test.web.servlet.assertj.MockMvcTester.MockMvcRequestBuilder;
import org.springframework.ui.Model;
import org.springframework.validation.Errors;
@@ -105,6 +106,9 @@ public class MockMvcTesterIntegrationTests {
@Nested
class PerformTests {
private final MockMultipartFile file = new MockMultipartFile("file", "content.txt", null,
"value".getBytes(StandardCharsets.UTF_8));
@Test
void syncRequestWithDefaultExchange() {
assertThat(mvc.get().uri("/greet")).hasStatusOk();
@@ -116,6 +120,13 @@ public class MockMvcTesterIntegrationTests {
.hasBodyTextEqualTo("name=Joe&someBoolean=true");
}
@Test
void asyncMultipartRequestWithDefaultExchange() {
assertThat(mvc.post().uri("/multipart-streaming").multipart()
.file(this.file).param("timeToWait", "100"))
.hasStatusOk().hasBodyTextEqualTo("name=Joe&file=content.txt");
}
@Test
void syncRequestWithExplicitExchange() {
assertThat(mvc.get().uri("/greet").exchange()).hasStatusOk();
@@ -127,6 +138,13 @@ public class MockMvcTesterIntegrationTests {
.hasStatusOk().hasBodyTextEqualTo("name=Joe&someBoolean=true");
}
@Test
void asyncMultipartRequestWitExplicitExchange() {
assertThat(mvc.post().uri("/multipart-streaming").multipart()
.file(this.file).param("timeToWait", "100").exchange())
.hasStatusOk().hasBodyTextEqualTo("name=Joe&file=content.txt");
}
@Test
void syncRequestWithExplicitExchangeIgnoresDuration() {
Duration timeToWait = mock(Duration.class);
@@ -140,6 +158,13 @@ public class MockMvcTesterIntegrationTests {
.hasStatusOk().hasBodyTextEqualTo("name=Joe&someBoolean=true");
}
@Test
void asyncMultipartRequestWithExplicitExchangeAndEnoughTimeToWait() {
assertThat(mvc.post().uri("/multipart-streaming").multipart()
.file(this.file).param("timeToWait", "100").exchange(Duration.ofMillis(200)))
.hasStatusOk().hasBodyTextEqualTo("name=Joe&file=content.txt");
}
@Test
void asyncRequestWithExplicitExchangeAndNotEnoughTimeToWait() {
MockMvcRequestBuilder builder = mvc.get().uri("/streaming").param("timeToWait", "500");
@@ -147,6 +172,15 @@ public class MockMvcTesterIntegrationTests {
.isThrownBy(() -> builder.exchange(Duration.ofMillis(100)))
.withMessageContaining("was not set during the specified timeToWait=100");
}
@Test
void asyncMultipartRequestWithExplicitExchangeAndNotEnoughTimeToWait() {
MockMultipartMvcRequestBuilder builder = mvc.post().uri("/multipart-streaming").multipart()
.file(this.file).param("timeToWait", "500");
assertThatIllegalStateException()
.isThrownBy(() -> builder.exchange(Duration.ofMillis(100)))
.withMessageContaining("was not set during the specified timeToWait=100");
}
}
@Nested
@@ -154,14 +188,13 @@ public class MockMvcTesterIntegrationTests {
@Test
void hasAsyncStartedTrue() {
// Need #perform as the regular exchange waits for async completion automatically
assertThat(mvc.perform(mvc.get().uri("/callable").accept(MediaType.APPLICATION_JSON)))
assertThat(mvc.get().uri("/callable").accept(MediaType.APPLICATION_JSON).asyncExchange())
.request().hasAsyncStarted(true);
}
@Test
void hasAsyncStartedFalse() {
assertThat(mvc.get().uri("/greet")).request().hasAsyncStarted(false);
assertThat(mvc.get().uri("/greet").asyncExchange()).request().hasAsyncStarted(false);
}
@Test
@@ -325,8 +358,7 @@ public class MockMvcTesterIntegrationTests {
@Test
void asyncResult() {
// Need #perform as the regular exchange waits for async completion automatically
MvcTestResult result = mvc.perform(mvc.get().uri("/callable").accept(MediaType.APPLICATION_JSON));
MvcTestResult result = mvc.get().uri("/callable").accept(MediaType.APPLICATION_JSON).asyncExchange();
assertThat(result.getMvcResult().getAsyncResult())
.asInstanceOf(InstanceOfAssertFactories.map(String.class, Object.class))
.containsOnly(entry("key", "value"));
@@ -694,9 +726,24 @@ public class MockMvcTesterIntegrationTests {
}
@PutMapping("/multipart-put")
public ModelAndView multiPartViaHttpPut(@RequestParam MultipartFile file) {
ModelAndView multiPartViaHttpPut(@RequestParam MultipartFile file) {
return new ModelAndView("index", Map.of("name", file.getName()));
}
@PostMapping("/multipart-streaming")
StreamingResponseBody streaming(@RequestParam MultipartFile file, @RequestParam long timeToWait) {
return out -> {
PrintStream stream = new PrintStream(out, true, StandardCharsets.UTF_8);
stream.print("name=Joe");
try {
Thread.sleep(timeToWait);
stream.print("&file=" + file.getOriginalFilename());
}
catch (InterruptedException e) {
/* no-op */
}
};
}
}
@Controller

View File

@@ -32,7 +32,6 @@ 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;
@@ -265,9 +264,7 @@ class AsyncTests {
void printAsyncResult() {
StringWriter asyncWriter = new StringWriter();
// Use #perform to not complete asynchronous request automatically
RequestBuilder requestBuilder = this.mockMvc.get().uri("/1").param("deferredResult", "true");
MvcTestResult result = this.mockMvc.perform(requestBuilder);
MvcTestResult result = this.mockMvc.get().uri("/1").param("deferredResult", "true").asyncExchange();
assertThat(result).debug(asyncWriter).request().hasAsyncStarted(true);
assertThat(asyncWriter.toString()).contains("Async started = true");
asyncWriter = new StringWriter(); // Reset