I have a comet(long polling) Controller call, which takes in some ids and puts then into a blocking queue if no calculation for that id is running, for a Consumer to take from the queue and perform computations on these ids. I am using Springs DeferredResult for asynch support.
I maintain a Map of DeferredResult and the corresponding ids that were received in a request. When the calculations for a id are complete in the consumer thread I check for this id in the Map and set the associated DeferredResults setResult which send the response back to the client.
In the Controller method I have an onCompletion callback of DeferredResult which removes this DeferredResult object from the Map.
The client then removes this id from its request and sends the remaining ids. As as example say the client send initially ids "1,2,3" they all got inserted into BlockingQueue and say the calculation for id "2" was finished earlier, then its DeferredResults setResult will be set, which will return the 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 ids "1, 3".
Now all works fine, but when I started writing test cases for this the onCompletion callback is never called. I even tried it in the springs official examples it does not seem to be called even there. Is there a way to call it, or something is incorrect 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;
}
Below is a part of the 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 receive the response as expected, but as the onCompletion callback is never called my logic of calling the method again by removing the id takes a hit as the Map holds on the past DeferredResult.
Update
The test class annotations are:
@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 call to getLongPollingGraphData is not completed, i.e. it does not wait for the specified timeout of 5 seconds and returns immediately causing the result to be null. I read about it and the recommendation is to add .andExpect(request().asyncResult("Expected output")) to mockMvc object.
But this is the whole point of my test case, I want the result to be calculated and returned so that I can resend a request modifying my ids variable based on the response received.