Skip to content

Commit 323fa85

Browse files
committed
Call complete() on MockAsyncContext after dispatch
Issue: SPR-13615
1 parent 546efc2 commit 323fa85

File tree

4 files changed

+144
-2
lines changed

4 files changed

+144
-2
lines changed

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

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818

1919
import java.util.ArrayList;
2020
import java.util.List;
21+
import javax.servlet.DispatcherType;
2122
import javax.servlet.Filter;
2223
import javax.servlet.ServletContext;
2324

@@ -147,14 +148,18 @@ public ResultActions perform(RequestBuilder requestBuilder) throws Exception {
147148
final MvcResult mvcResult = new DefaultMvcResult(request, response);
148149
request.setAttribute(MVC_RESULT_ATTRIBUTE, mvcResult);
149150

150-
// [SPR-13217] Simulate RequestContextFilter to ensure that RequestAttributes are
151-
// populated before filters are invoked.
152151
RequestAttributes previousAttributes = RequestContextHolder.getRequestAttributes();
153152
RequestContextHolder.setRequestAttributes(new ServletRequestAttributes(request, response));
154153

155154
MockFilterChain filterChain = new MockFilterChain(this.servlet, this.filters);
156155
filterChain.doFilter(request, response);
157156

157+
if (DispatcherType.ASYNC.equals(request.getDispatcherType()) &&
158+
request.getAsyncContext() != null & !request.isAsyncStarted()) {
159+
160+
request.getAsyncContext().complete();
161+
}
162+
158163
applyDefaultResultActions(mvcResult);
159164

160165
RequestContextHolder.setRequestAttributes(previousAttributes);

spring-test/src/main/java/org/springframework/test/web/servlet/request/MockMvcRequestBuilders.java

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
package org.springframework.test.web.servlet.request;
1818

1919
import java.net.URI;
20+
import javax.servlet.DispatcherType;
2021
import javax.servlet.ServletContext;
2122

2223
import org.springframework.http.HttpMethod;
@@ -237,6 +238,7 @@ public static RequestBuilder asyncDispatch(final MvcResult mvcResult) {
237238
@Override
238239
public MockHttpServletRequest buildRequest(ServletContext servletContext) {
239240
MockHttpServletRequest request = mvcResult.getRequest();
241+
request.setDispatcherType(DispatcherType.ASYNC);
240242
request.setAsyncStarted(false);
241243
return request;
242244
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
/*
2+
* Copyright 2002-2015 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.test.web.servlet.samples.context;
18+
19+
import java.util.Collections;
20+
import java.util.Map;
21+
import java.util.concurrent.Callable;
22+
23+
import org.junit.Before;
24+
import org.junit.Test;
25+
import org.junit.runner.RunWith;
26+
import org.mockito.Mockito;
27+
28+
import org.springframework.beans.factory.annotation.Autowired;
29+
import org.springframework.context.annotation.Bean;
30+
import org.springframework.context.annotation.Configuration;
31+
import org.springframework.http.MediaType;
32+
import org.springframework.test.context.ContextConfiguration;
33+
import org.springframework.test.context.ContextHierarchy;
34+
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
35+
import org.springframework.test.context.web.WebAppConfiguration;
36+
import org.springframework.test.web.servlet.MockMvc;
37+
import org.springframework.test.web.servlet.MvcResult;
38+
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
39+
import org.springframework.web.bind.annotation.RequestMapping;
40+
import org.springframework.web.bind.annotation.RestController;
41+
import org.springframework.web.context.WebApplicationContext;
42+
import org.springframework.web.context.request.async.CallableProcessingInterceptor;
43+
import org.springframework.web.servlet.config.annotation.AsyncSupportConfigurer;
44+
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
45+
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
46+
47+
import static org.mockito.Matchers.any;
48+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch;
49+
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
50+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.content;
51+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.request;
52+
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;
53+
54+
/**
55+
* Tests with Java configuration.
56+
*
57+
* @author Rossen Stoyanchev
58+
*/
59+
@RunWith(SpringJUnit4ClassRunner.class)
60+
@WebAppConfiguration
61+
@ContextHierarchy(@ContextConfiguration(classes = AsyncControllerJavaConfigTests.WebConfig.class))
62+
public class AsyncControllerJavaConfigTests {
63+
64+
@Autowired
65+
private WebApplicationContext wac;
66+
67+
@Autowired
68+
private CallableProcessingInterceptor callableInterceptor;
69+
70+
private MockMvc mockMvc;
71+
72+
73+
@Before
74+
public void setup() {
75+
this.mockMvc = MockMvcBuilders.webAppContextSetup(this.wac).build();
76+
}
77+
78+
// SPR-13615
79+
80+
@Test
81+
public void callableInterceptor() throws Exception {
82+
MvcResult mvcResult = this.mockMvc.perform(get("/callable").accept(MediaType.APPLICATION_JSON))
83+
.andExpect(status().isOk())
84+
.andExpect(request().asyncStarted())
85+
.andExpect(request().asyncResult(Collections.singletonMap("key", "value")))
86+
.andReturn();
87+
88+
Mockito.verify(this.callableInterceptor).beforeConcurrentHandling(any(), any());
89+
Mockito.verify(this.callableInterceptor).preProcess(any(), any());
90+
Mockito.verify(this.callableInterceptor).postProcess(any(), any(), any());
91+
Mockito.verifyNoMoreInteractions(this.callableInterceptor);
92+
93+
this.mockMvc.perform(asyncDispatch(mvcResult))
94+
.andExpect(status().isOk())
95+
.andExpect(content().string("{\"key\":\"value\"}"));
96+
97+
Mockito.verify(this.callableInterceptor).afterCompletion(any(), any());
98+
Mockito.verifyNoMoreInteractions(this.callableInterceptor);
99+
}
100+
101+
102+
@Configuration
103+
@EnableWebMvc
104+
@SuppressWarnings("unused")
105+
static class WebConfig extends WebMvcConfigurerAdapter {
106+
107+
@Override
108+
public void configureAsyncSupport(AsyncSupportConfigurer configurer) {
109+
configurer.registerCallableInterceptors(callableInterceptor());
110+
}
111+
112+
@Bean
113+
public CallableProcessingInterceptor callableInterceptor() {
114+
return Mockito.mock(CallableProcessingInterceptor.class);
115+
}
116+
117+
@Bean
118+
public AsyncController asyncController() {
119+
return new AsyncController();
120+
}
121+
122+
}
123+
124+
@RestController
125+
@SuppressWarnings("unused")
126+
static class AsyncController {
127+
128+
@RequestMapping(path = "/callable")
129+
public Callable<Map<String, String>> getCallable() {
130+
return () -> Collections.singletonMap("key", "value");
131+
}
132+
}
133+
134+
}

spring-test/src/test/java/org/springframework/test/web/servlet/samples/context/JavaConfigTests.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,7 @@ public PersonDao personDao() {
143143

144144
@Configuration
145145
@EnableWebMvc
146+
@SuppressWarnings("unused")
146147
static class WebConfig extends WebMvcConfigurerAdapter {
147148

148149
@Autowired

0 commit comments

Comments
 (0)