问题
I'm implementing long polling as per the Spring blog from some time ago.
Here my converted method with same response signature as before, but instead of responding immediately, it now uses long polling:
private Map<String, DeferredResult<ResponseEntity<?>>> requests = new ConcurrentHashMap<>();
@RequestMapping(value = "/{uuid}", method = RequestMethod.GET)
public DeferredResult<ResponseEntity<?>> poll(@PathVariable("uuid") final String uuid) {
// Create & store a new instance
ResponseEntity<?> pendingOnTimeout = ResponseEntity.accepted().build();
DeferredResult<ResponseEntity<?>> deferredResult = new DeferredResult<>(TWENTYFIVE_SECONDS, pendingOnTimeout);
requests.put(uuid, deferredResult);
// Clean up poll requests when done
deferredResult.onCompletion(() -> {
requests.remove(deferredResult);
});
// Set result if already available
Task task = taskHolder.retrieve(uuid);
if (task == null)
deferredResult.setResult(ResponseEntity.status(HttpStatus.GONE).build());
else
// Done (or canceled): Redirect to retrieve file contents
if (task.getFutureFile().isDone())
deferredResult.setResult(ResponseEntity.created(RetrieveController.uri(uuid)).build());
// Return result
return deferredResult;
}
In particular I'd like to return the pendingOnTimeout
response when the request takes too long (which I returned immediately before), to prevent proxies from cutting off the request.
Now I think I've gotten this working as is, but I'd like to write a unittest that confirms this. However all my attempts at using MockMvc (via webAppContextSetup) fail to provide me with a means of asserting that I get an accepted
header. When I for instance try the following:
@Test
public void pollPending() throws Exception {
MvcResult result = mockMvc.perform(get("/poll/{uuid}", uuidPending)).andReturn();
mockMvc.perform(asyncDispatch(result))
.andExpect(status().isAccepted());
}
I get the following stacktrace:
java.lang.IllegalStateException: Async result for handler [public org.springframework.web.context.request.async.DeferredResult> nl.bioprodict.blast.api.PollController.poll(java.lang.String)] was not set during the specified timeToWait=25000 at org.springframework.util.Assert.state(Assert.java:392) at org.springframework.test.web.servlet.DefaultMvcResult.getAsyncResult(DefaultMvcResult.java:143) at org.springframework.test.web.servlet.DefaultMvcResult.getAsyncResult(DefaultMvcResult.java:120) at org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch(MockMvcRequestBuilders.java:235) at nl.bioprodict.blast.docs.PollControllerDocumentation.pollPending(PollControllerDocumentation.java:53) ...
The Spring framework tests related to this that I could find all use mocking it seems: https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTimeoutTests.java
How can I test the correct handling of the DeferredResult timeoutResult?
回答1:
In my case, after going through spring source code and setting the timeout (10000 millisecond) and getting async result solved it for me, as;
mvcResult.getRequest().getAsyncContext().setTimeout(10000);
mvcResult.getAsyncResult();
My whole test code was;
MvcResult mvcResult = this.mockMvc.perform(
post("<SOME_RELATIVE_URL>")
.contentType(MediaType.APPLICATION_JSON)
.content(<JSON_DATA>))
***.andExpect(request().asyncStarted())***
.andReturn();
***mvcResult.getRequest().getAsyncContext().setTimeout(10000);***
***mvcResult.getAsyncResult();***
this.mockMvc
.perform(asyncDispatch(mvcResult))
.andDo(print())
.andExpect(status().isOk());
Hope it helps..
回答2:
I ran across this problem using Spring 4.3, and managed to find a way to trigger the timeout callback from within the unit test. After getting the MvcResult
, and before calling asyncDispatch()
, you can insert code such as the following:
MockAsyncContext ctx = (MockAsyncContext) mvcResult.getRequest().getAsyncContext();
for (AsyncListener listener : ctx.getListeners()) {
listener.onTimeout(null);
}
One of the async listeners for the request will invoke the DeferredResult
's timeout callback.
So your unit test would look like this:
@Test
public void pollPending() throws Exception {
MvcResult result = mockMvc.perform(get("/poll/{uuid}", uuidPending)).andReturn();
MockAsyncContext ctx = (MockAsyncContext) result.getRequest().getAsyncContext();
for (AsyncListener listener : ctx.getListeners()) {
listener.onTimeout(null);
}
mockMvc.perform(asyncDispatch(result))
.andExpect(status().isAccepted());
}
来源:https://stackoverflow.com/questions/34343231/how-to-test-deferredresult-timeoutresult