Skip to content

Commit fab8890

Browse files
committed
Support multiple async starts in MockHttpServletRequest
Closes gh-33457
1 parent 5927b70 commit fab8890

File tree

3 files changed

+98
-2
lines changed

3 files changed

+98
-2
lines changed

spring-test/src/main/java/org/springframework/mock/web/MockHttpServletRequest.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import java.util.stream.Collectors;
4545

4646
import jakarta.servlet.AsyncContext;
47+
import jakarta.servlet.AsyncEvent;
48+
import jakarta.servlet.AsyncListener;
4749
import jakarta.servlet.DispatcherType;
4850
import jakarta.servlet.RequestDispatcher;
4951
import jakarta.servlet.ServletConnection;
@@ -920,7 +922,19 @@ public AsyncContext startAsync() {
920922
public AsyncContext startAsync(ServletRequest request, @Nullable ServletResponse response) {
921923
Assert.state(this.asyncSupported, "Async not supported");
922924
this.asyncStarted = true;
923-
this.asyncContext = new MockAsyncContext(request, response);
925+
MockAsyncContext newAsyncContext = new MockAsyncContext(request, response);
926+
if (this.asyncContext != null) {
927+
try {
928+
AsyncEvent startEvent = new AsyncEvent(newAsyncContext);
929+
for (AsyncListener asyncListener : this.asyncContext.getListeners()) {
930+
asyncListener.onStartAsync(startEvent);
931+
}
932+
}
933+
catch (IOException ex) {
934+
// ignore failures
935+
}
936+
}
937+
this.asyncContext = newAsyncContext;
924938
return this.asyncContext;
925939
}
926940

spring-test/src/test/java/org/springframework/mock/web/MockHttpServletRequestTests.java

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
import java.util.Locale;
3131
import java.util.Map;
3232

33+
import jakarta.servlet.AsyncContext;
34+
import jakarta.servlet.AsyncEvent;
35+
import jakarta.servlet.AsyncListener;
3336
import jakarta.servlet.http.Cookie;
3437
import org.junit.jupiter.api.Test;
3538

@@ -663,6 +666,44 @@ void httpHeaderFormattedDateError() {
663666
request.getDateHeader(HttpHeaders.IF_MODIFIED_SINCE));
664667
}
665668

669+
@Test
670+
void shouldRejectAsyncStartsIfUnsupported() {
671+
assertThat(request.isAsyncStarted()).isFalse();
672+
assertThatIllegalStateException().isThrownBy(request::startAsync);
673+
}
674+
675+
@Test
676+
void startAsyncShouldUpdateRequestState() {
677+
assertThat(request.isAsyncStarted()).isFalse();
678+
request.setAsyncSupported(true);
679+
AsyncContext asyncContext = request.startAsync();
680+
assertThat(request.isAsyncStarted()).isTrue();
681+
}
682+
683+
@Test
684+
void shouldNotifyAsyncListeners() {
685+
request.setAsyncSupported(true);
686+
AsyncContext asyncContext = request.startAsync();
687+
TestAsyncListener testAsyncListener = new TestAsyncListener();
688+
asyncContext.addListener(testAsyncListener);
689+
asyncContext.complete();
690+
assertThat(testAsyncListener.events).hasSize(1);
691+
assertThat(testAsyncListener.events.get(0)).extracting("name").isEqualTo("onComplete");
692+
}
693+
694+
@Test
695+
void shouldNotifyAsyncListenersWhenNewAsyncStarted() {
696+
request.setAsyncSupported(true);
697+
AsyncContext asyncContext = request.startAsync();
698+
TestAsyncListener testAsyncListener = new TestAsyncListener();
699+
asyncContext.addListener(testAsyncListener);
700+
AsyncContext newAsyncContext = request.startAsync();
701+
assertThat(testAsyncListener.events).hasSize(1);
702+
ListenerEvent listenerEvent = testAsyncListener.events.get(0);
703+
assertThat(listenerEvent).extracting("name").isEqualTo("onStartAsync");
704+
assertThat(listenerEvent.event.getAsyncContext()).isEqualTo(newAsyncContext);
705+
}
706+
666707
private void assertEqualEnumerations(Enumeration<?> enum1, Enumeration<?> enum2) {
667708
int count = 0;
668709
while (enum1.hasMoreElements()) {
@@ -672,4 +713,31 @@ private void assertEqualEnumerations(Enumeration<?> enum1, Enumeration<?> enum2)
672713
}
673714
}
674715

716+
static class TestAsyncListener implements AsyncListener {
717+
718+
List<ListenerEvent> events = new ArrayList<>();
719+
720+
@Override
721+
public void onComplete(AsyncEvent asyncEvent) throws IOException {
722+
this.events.add(new ListenerEvent("onComplete", asyncEvent));
723+
}
724+
725+
@Override
726+
public void onTimeout(AsyncEvent asyncEvent) throws IOException {
727+
this.events.add(new ListenerEvent("onTimeout", asyncEvent));
728+
}
729+
730+
@Override
731+
public void onError(AsyncEvent asyncEvent) throws IOException {
732+
this.events.add(new ListenerEvent("onError", asyncEvent));
733+
}
734+
735+
@Override
736+
public void onStartAsync(AsyncEvent asyncEvent) throws IOException {
737+
this.events.add(new ListenerEvent("onStartAsync", asyncEvent));
738+
}
739+
}
740+
741+
record ListenerEvent(String name, AsyncEvent event) {}
742+
675743
}

spring-web/src/testFixtures/java/org/springframework/web/testfixture/servlet/MockHttpServletRequest.java

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,8 @@
4444
import java.util.stream.Collectors;
4545

4646
import jakarta.servlet.AsyncContext;
47+
import jakarta.servlet.AsyncEvent;
48+
import jakarta.servlet.AsyncListener;
4749
import jakarta.servlet.DispatcherType;
4850
import jakarta.servlet.RequestDispatcher;
4951
import jakarta.servlet.ServletConnection;
@@ -921,7 +923,19 @@ public AsyncContext startAsync() {
921923
public AsyncContext startAsync(ServletRequest request, @Nullable ServletResponse response) {
922924
Assert.state(this.asyncSupported, "Async not supported");
923925
this.asyncStarted = true;
924-
this.asyncContext = new MockAsyncContext(request, response);
926+
MockAsyncContext newAsyncContext = new MockAsyncContext(request, response);
927+
if (this.asyncContext != null) {
928+
try {
929+
AsyncEvent startEvent = new AsyncEvent(newAsyncContext);
930+
for (AsyncListener asyncListener : this.asyncContext.getListeners()) {
931+
asyncListener.onStartAsync(startEvent);
932+
}
933+
}
934+
catch (IOException ex) {
935+
// ignore failures
936+
}
937+
}
938+
this.asyncContext = newAsyncContext;
925939
return this.asyncContext;
926940
}
927941

0 commit comments

Comments
 (0)