Spring DeferredResult onCompletion not called from test cases

I have a comet (long poll) Controller call, which accepts some identifiers and puts then in the lock queue, if calculations for this id are not performed, for Consumer to take from the queue and perform calculations on these identifiers. I am using Springs DeferredResult to support asynch.

I support Map DeferredResult and the corresponding identifiers received in the request. When the calculations for id are completed in the consumer stream, I check this identifier in Map and set the associated DeferredResults setResult , which sends a response to the client.

In the Controller method, I have an onCompletion DeferredResult callback that removes this DeferredResult object from the Map .

The client then removes this identifier from its request and sends the remaining identifiers. As an example, the client sends the identifiers "1,2,3" initially, they are all inserted into the BlockingQueue and say that the calculation for id "2" was completed earlier, then its DeferredResults setResult will be set, which will return a response to the client. And through the callback, this DeferredResult will be removed from the Map . And the client in the next request will send the identifiers "1, 3".

Now everything works fine, but when I started writing test cases for this, the onCompletion callback is never called. I even tried it in official examples of springs, it seems to not be called even there. Is there a way to call this, or is something wrong in my implementation.

This is my Controller method:

 @RequestMapping(value = "views/getLongPollingGraphData", method = RequestMethod.GET, headers = "Accept=application/json") @ResponseBody public DeferredResult<WebServiceResponse> getLongGraphData( HttpServletRequest request, @RequestParam(value = "ids") String ids) { //create a default response in case, when no result has been calculated till timeout WebServiceResponse awrDefault = new WebServiceResponse(); //set time out this DeferredResult, after 5 seconds final DeferredResult<WebServiceResponse> deferredResult = new DeferredResult<WebServiceResponse>(5000L, awrDefault); //logic to set in blocking queue //mMapOfDeferredResultAndViews.put(deferredResult, listOfViews.keySet()); deferredResult.onCompletion(new Runnable() { @Override public void run() { mMapOfDeferredResultAndViews.remove(deferredResult); } }); return deferredResult; } 

The following is part of a test case:

 @Before public void setup() { this.mockMvc = webAppContextSetup(this.wac).build(); } @Test public void testLongPollGraphData() { try { String ids = "22,23,25"; List<Integer> idList = convertStringToList(ids); while(idList.size() > 0) { MvcResult mvcResult = this.mockMvc.perform(get("/views/getLongPollingGraphData") .contentType(MediaType.APPLICATION_JSON) .param("ids", ids) .andReturn(); this.mockMvc.perform(asyncDispatch(mvcResult)); WebServiceResponse result = (WebServiceResponse)mvcResult.getAsyncResult(); if(result != null) { EJSChartsData chartsData = (EJSChartsData)result.getResponse(); if(chartsData != null && chartsData.getViewId() != -1) { int viewId = chartsData.getViewId(); idList.remove((Integer)viewId); ids = idList.toString().replace("[", "").replace("]", ""); } } } } catch(Exception e) { fail(e.toString()); } } 

The Controller method is called, and I get the answer as expected, but since the onCompletion callback is never called, my logic of calling the method again by removing the identifier takes a hit, since Map is executed on the past DeferredResult .

Update

Test class annotations:

 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "/test-context.xml" }) @WebAppConfiguration @TransactionConfiguration(transactionManager = "transactionManager", defaultRollback = true) @Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW) 

I have another observation, the getLongPollingGraphData call getLongPollingGraphData not completed, that is, it does not wait for the specified timeout of 5 seconds and immediately returns that the result will be null . I read about this, and it is recommended that you add the .andExpect(request().asyncResult("Expected output")) mockMvc to mockMvc .

But this is the whole point of my test case, I want the result to be calculated and returned, so that I can resubmit the request, changing my identification variable based on the response received.

+6
source share
1 answer

What version of Spring are you using? I opened SPR-13615 for a similar case, and it is fixed in 4.2.3 (affects 4.2.2).

If you look at the comments, you can get around this without updating using

  mvcResult.getRequest().getAsyncContext().complete(); 
0
source

All Articles