Skip to content

Commit 24bbc6d

Browse files
committed
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
1 parent a7503e7 commit 24bbc6d

File tree

3 files changed

+80
-10
lines changed

3 files changed

+80
-10
lines changed

spring-test/src/main/java/org/springframework/test/web/servlet/assertj/MockMvcTester.java

+26
Original file line numberDiff line numberDiff line change
@@ -448,6 +448,8 @@ public MockMultipartMvcRequestBuilder multipart() {
448448
* assertThat(mvc.get().uri("/greet")).hasStatusOk();
449449
* assertThat(mvc.get().uri("/greet").exchange()).hasStatusOk();
450450
* </code></pre>
451+
* <p>For assertions on the original asynchronous request that might
452+
* still be in progress, use {@link #asyncExchange()}.
451453
* @see #exchange(Duration) to customize the timeout for async requests
452454
*/
453455
public MvcTestResult exchange() {
@@ -458,12 +460,23 @@ public MvcTestResult exchange() {
458460
* Execute the request and wait at most the given {@code timeToWait}
459461
* duration for the asynchronous request to complete. If the request
460462
* is not asynchronous, the {@code timeToWait} is ignored.
463+
* <p>For assertions on the original asynchronous request that might
464+
* still be in progress, use {@link #asyncExchange()}.
461465
* @see #exchange()
462466
*/
463467
public MvcTestResult exchange(Duration timeToWait) {
464468
return MockMvcTester.this.exchange(this, timeToWait);
465469
}
466470

471+
/**
472+
* Execute the request and do not attempt to wait for the completion of
473+
* an asynchronous request. Contrary to {@link #exchange()}, this returns
474+
* the original result that might still be in progress.
475+
*/
476+
public MvcTestResult asyncExchange() {
477+
return MockMvcTester.this.perform(this);
478+
}
479+
467480
@Override
468481
public MvcTestResultAssert assertThat() {
469482
return new MvcTestResultAssert(exchange(), MockMvcTester.this.jsonMessageConverter);
@@ -493,6 +506,8 @@ private MockMultipartMvcRequestBuilder(MockMvcRequestBuilder currentBuilder) {
493506
* assertThat(mvc.get().uri("/greet")).hasStatusOk();
494507
* assertThat(mvc.get().uri("/greet").exchange()).hasStatusOk();
495508
* </code></pre>
509+
* <p>For assertions on the original asynchronous request that might
510+
* still be in progress, use {@link #asyncExchange()}.
496511
* @see #exchange(Duration) to customize the timeout for async requests
497512
*/
498513
public MvcTestResult exchange() {
@@ -503,12 +518,23 @@ public MvcTestResult exchange() {
503518
* Execute the request and wait at most the given {@code timeToWait}
504519
* duration for the asynchronous request to complete. If the request
505520
* is not asynchronous, the {@code timeToWait} is ignored.
521+
* <p>For assertions on the original asynchronous request that might
522+
* still be in progress, use {@link #asyncExchange()}.
506523
* @see #exchange()
507524
*/
508525
public MvcTestResult exchange(Duration timeToWait) {
509526
return MockMvcTester.this.exchange(this, timeToWait);
510527
}
511528

529+
/**
530+
* Execute the request and do not attempt to wait for the completion of
531+
* an asynchronous request. Contrary to {@link #exchange()}, this returns
532+
* the original result that might still be in progress.
533+
*/
534+
public MvcTestResult asyncExchange() {
535+
return MockMvcTester.this.perform(this);
536+
}
537+
512538
@Override
513539
public MvcTestResultAssert assertThat() {
514540
return new MvcTestResultAssert(exchange(), MockMvcTester.this.jsonMessageConverter);

spring-test/src/test/java/org/springframework/test/web/servlet/assertj/MockMvcTesterIntegrationTests.java

+53-6
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@
5555
import org.springframework.test.context.junit.jupiter.web.SpringJUnitWebConfig;
5656
import org.springframework.test.web.Person;
5757
import org.springframework.test.web.servlet.ResultMatcher;
58+
import org.springframework.test.web.servlet.assertj.MockMvcTester.MockMultipartMvcRequestBuilder;
5859
import org.springframework.test.web.servlet.assertj.MockMvcTester.MockMvcRequestBuilder;
5960
import org.springframework.ui.Model;
6061
import org.springframework.validation.Errors;
@@ -105,6 +106,9 @@ public class MockMvcTesterIntegrationTests {
105106
@Nested
106107
class PerformTests {
107108

109+
private final MockMultipartFile file = new MockMultipartFile("file", "content.txt", null,
110+
"value".getBytes(StandardCharsets.UTF_8));
111+
108112
@Test
109113
void syncRequestWithDefaultExchange() {
110114
assertThat(mvc.get().uri("/greet")).hasStatusOk();
@@ -116,6 +120,13 @@ void asyncRequestWithDefaultExchange() {
116120
.hasBodyTextEqualTo("name=Joe&someBoolean=true");
117121
}
118122

123+
@Test
124+
void asyncMultipartRequestWithDefaultExchange() {
125+
assertThat(mvc.post().uri("/multipart-streaming").multipart()
126+
.file(this.file).param("timeToWait", "100"))
127+
.hasStatusOk().hasBodyTextEqualTo("name=Joe&file=content.txt");
128+
}
129+
119130
@Test
120131
void syncRequestWithExplicitExchange() {
121132
assertThat(mvc.get().uri("/greet").exchange()).hasStatusOk();
@@ -127,6 +138,13 @@ void asyncRequestWithExplicitExchange() {
127138
.hasStatusOk().hasBodyTextEqualTo("name=Joe&someBoolean=true");
128139
}
129140

141+
@Test
142+
void asyncMultipartRequestWitExplicitExchange() {
143+
assertThat(mvc.post().uri("/multipart-streaming").multipart()
144+
.file(this.file).param("timeToWait", "100").exchange())
145+
.hasStatusOk().hasBodyTextEqualTo("name=Joe&file=content.txt");
146+
}
147+
130148
@Test
131149
void syncRequestWithExplicitExchangeIgnoresDuration() {
132150
Duration timeToWait = mock(Duration.class);
@@ -140,28 +158,43 @@ void asyncRequestWithExplicitExchangeAndEnoughTimeToWait() {
140158
.hasStatusOk().hasBodyTextEqualTo("name=Joe&someBoolean=true");
141159
}
142160

161+
@Test
162+
void asyncMultipartRequestWithExplicitExchangeAndEnoughTimeToWait() {
163+
assertThat(mvc.post().uri("/multipart-streaming").multipart()
164+
.file(this.file).param("timeToWait", "100").exchange(Duration.ofMillis(200)))
165+
.hasStatusOk().hasBodyTextEqualTo("name=Joe&file=content.txt");
166+
}
167+
143168
@Test
144169
void asyncRequestWithExplicitExchangeAndNotEnoughTimeToWait() {
145170
MockMvcRequestBuilder builder = mvc.get().uri("/streaming").param("timeToWait", "500");
146171
assertThatIllegalStateException()
147172
.isThrownBy(() -> builder.exchange(Duration.ofMillis(100)))
148173
.withMessageContaining("was not set during the specified timeToWait=100");
149174
}
175+
176+
@Test
177+
void asyncMultipartRequestWithExplicitExchangeAndNotEnoughTimeToWait() {
178+
MockMultipartMvcRequestBuilder builder = mvc.post().uri("/multipart-streaming").multipart()
179+
.file(this.file).param("timeToWait", "500");
180+
assertThatIllegalStateException()
181+
.isThrownBy(() -> builder.exchange(Duration.ofMillis(100)))
182+
.withMessageContaining("was not set during the specified timeToWait=100");
183+
}
150184
}
151185

152186
@Nested
153187
class RequestTests {
154188

155189
@Test
156190
void hasAsyncStartedTrue() {
157-
// Need #perform as the regular exchange waits for async completion automatically
158-
assertThat(mvc.perform(mvc.get().uri("/callable").accept(MediaType.APPLICATION_JSON)))
191+
assertThat(mvc.get().uri("/callable").accept(MediaType.APPLICATION_JSON).asyncExchange())
159192
.request().hasAsyncStarted(true);
160193
}
161194

162195
@Test
163196
void hasAsyncStartedFalse() {
164-
assertThat(mvc.get().uri("/greet")).request().hasAsyncStarted(false);
197+
assertThat(mvc.get().uri("/greet").asyncExchange()).request().hasAsyncStarted(false);
165198
}
166199

167200
@Test
@@ -325,8 +358,7 @@ class BodyTests {
325358

326359
@Test
327360
void asyncResult() {
328-
// Need #perform as the regular exchange waits for async completion automatically
329-
MvcTestResult result = mvc.perform(mvc.get().uri("/callable").accept(MediaType.APPLICATION_JSON));
361+
MvcTestResult result = mvc.get().uri("/callable").accept(MediaType.APPLICATION_JSON).asyncExchange();
330362
assertThat(result.getMvcResult().getAsyncResult())
331363
.asInstanceOf(InstanceOfAssertFactories.map(String.class, Object.class))
332364
.containsOnly(entry("key", "value"));
@@ -694,9 +726,24 @@ ModelAndView part(@RequestPart Part part, @RequestPart Map<String, String> json)
694726
}
695727

696728
@PutMapping("/multipart-put")
697-
public ModelAndView multiPartViaHttpPut(@RequestParam MultipartFile file) {
729+
ModelAndView multiPartViaHttpPut(@RequestParam MultipartFile file) {
698730
return new ModelAndView("index", Map.of("name", file.getName()));
699731
}
732+
733+
@PostMapping("/multipart-streaming")
734+
StreamingResponseBody streaming(@RequestParam MultipartFile file, @RequestParam long timeToWait) {
735+
return out -> {
736+
PrintStream stream = new PrintStream(out, true, StandardCharsets.UTF_8);
737+
stream.print("name=Joe");
738+
try {
739+
Thread.sleep(timeToWait);
740+
stream.print("&file=" + file.getOriginalFilename());
741+
}
742+
catch (InterruptedException e) {
743+
/* no-op */
744+
}
745+
};
746+
}
700747
}
701748

702749
@Controller

spring-test/src/test/java/org/springframework/test/web/servlet/samples/standalone/AsyncTests.java

+1-4
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,6 @@
3232
import org.springframework.test.web.Person;
3333
import org.springframework.test.web.servlet.MockMvc;
3434
import org.springframework.test.web.servlet.MvcResult;
35-
import org.springframework.test.web.servlet.RequestBuilder;
3635
import org.springframework.test.web.servlet.assertj.MockMvcTester;
3736
import org.springframework.test.web.servlet.assertj.MvcTestResult;
3837
import org.springframework.web.bind.annotation.ExceptionHandler;
@@ -265,9 +264,7 @@ void completableFutureWithImmediateValue() {
265264
void printAsyncResult() {
266265
StringWriter asyncWriter = new StringWriter();
267266

268-
// Use #perform to not complete asynchronous request automatically
269-
RequestBuilder requestBuilder = this.mockMvc.get().uri("/1").param("deferredResult", "true");
270-
MvcTestResult result = this.mockMvc.perform(requestBuilder);
267+
MvcTestResult result = this.mockMvc.get().uri("/1").param("deferredResult", "true").asyncExchange();
271268
assertThat(result).debug(asyncWriter).request().hasAsyncStarted(true);
272269
assertThat(asyncWriter.toString()).contains("Async started = true");
273270
asyncWriter = new StringWriter(); // Reset

0 commit comments

Comments
 (0)