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.