如何测试 DeferredResult timeoutResult

Posted

技术标签:

【中文标题】如何测试 DeferredResult timeoutResult【英文标题】:How to test DeferredResult timeoutResult 【发布时间】:2016-03-24 10:21:26 【问题描述】:

我正在实施long polling as per the Spring blog from some time ago。

这里我转换的方法与以前的响应签名相同,但不是立即响应,而是现在使用长轮询:

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;

我特别想在请求时间过长时返回 pendingOnTimeout 响应(我之前立即返回),以防止代理切断请求。

现在我想我已经按原样工作了,但我想编写一个单元测试来证实这一点。然而,我所有使用 MockMvc(通过 webAppContextSetup)的尝试都未能为我提供一种断言我得到 accepted 标头的方法。例如,当我尝试以下操作时:

@Test
public void pollPending() throws Exception 
    MvcResult result = mockMvc.perform(get("/poll/uuid", uuidPending)).andReturn();
    mockMvc.perform(asyncDispatch(result))
            .andExpect(status().isAccepted());

我得到以下堆栈跟踪:

java.lang.IllegalStateException: 处理程序 [public org.springframework.web.context.request.async.DeferredResult> nl.bioprodict.blast.api.PollController.poll(java.lang.String)] 的异步结果不是在指定时间内设置ToWait=25000 在 org.springframework.util.Assert.state(Assert.java:392) 在 org.springframework.test.web.servlet.DefaultMvcResult.getAsyncResult(DefaultMvcResult.java:143) 在 org.springframework.test.web.servlet.DefaultMvcResult.getAsyncResult(DefaultMvcResult.java:120) 在 org.springframework.test.web.servlet.request.MockMvcRequestBuilders.asyncDispatch(MockMvcRequestBuilders.java:235) 在 nl.bioprodict.blast.docs.PollControllerDocumentation.pollPending(PollControllerDocumentation.java:53) ...

与此相关的 Spring 框架测试似乎都在使用模拟:https://github.com/spring-projects/spring-framework/blob/master/spring-web/src/test/java/org/springframework/web/context/request/async/WebAsyncManagerTimeoutTests.java

的正确处理方式?

【问题讨论】:

要明确:它似乎在集成测试中运行良好,但我也想在 spring-restdocs-mockmvc 中测试它。 我刚刚遇到了同样的问题。您是否找到了允许在 DeferredResults 上测试超时的解决方案? @John 不,还没有,虽然我现在已经停止寻找了。如果你找到任何东西,请告诉我! @Tim 我需要测试同样的情况,你能找到解决方案吗? @Tim,我刚刚收到同样的错误,原因是DeferredResult 中的引用是null。希望对您有所帮助。 【参考方案1】:

在我的情况下,通过 spring 源代码并设置超时(10000 毫秒)并获得异步结果为我解决了它,因为;

 mvcResult.getRequest().getAsyncContext().setTimeout(10000);
 mvcResult.getAsyncResult();

我的整个测试代码是;

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());

希望对你有帮助..

【讨论】:

谢谢!赞成,因为我觉得它可能会帮助某人,但就我而言,它还不适用于 @SpringBootTest()@WebMvcTest(PollController.class)。两人都在呕吐:Deferred result was not set during the specified timeToWait=25000.. 不过还是谢谢!【参考方案2】:

我ran across this problem 使用 Spring 4.3,并设法从单元测试中找到触发超时回调的方法。在得到MvcResult之后,在调用asyncDispatch()之前,可以插入如下代码:

MockAsyncContext ctx = (MockAsyncContext) mvcResult.getRequest().getAsyncContext();
for (AsyncListener listener : ctx.getListeners()) 
    listener.onTimeout(null);

请求的异步侦听器之一将调用DeferredResult 的超时回调。

所以你的单元测试应该是这样的:

@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());

【讨论】:

这应该是公认的答案,是唯一适合我的答案

以上是关于如何测试 DeferredResult timeoutResult的主要内容,如果未能解决你的问题,请参考以下文章

为啥 DeferredResult 在尝试使用 SSE 时以 setResult() 结束

关于DeferredResult的思考

DeferredResult 实现长轮询

DeferredResult使用方式和场景

Spring DeferredResult 异步请求

使用DeferredResult实现异步处理REST服务示例