简体   繁体   English

集成测试 Spring SseEmitters

[英]Integration Testing Spring SseEmitters

I've been looking for hints on how to best test Spring MVC Controller methods that return SseEmitters.我一直在寻找有关如何最好地测试返回 SseEmitters 的 Spring MVC 控制器方法的提示。 I have come up pretty short, but have a trial-and-error solution that tests against asynchronous, threaded behavior.我的想法很短,但有一个反复试验的解决方案,可以测试异步、线程化的行为。 The below is sample code just to demonstrate concept, there may be a typo or two:下面是示例代码,只是为了演示概念,可能有一两个错字:

Controller Class:控制器类:

@Autowired
Publisher<MyResponse> responsePublisher;

@RequestMapping("/mypath")
public SseEmitter index() throws IOException {
    SseEmitter emitter = new SseEmitter();
    Observable<MyResponse> responseObservable = RxReactiveStreams.toObservable(responsePublisher);

    responseObservable.subscribe(
            response -> {
                try {
                    emitter.send(response);
               } catch (IOException ex) {
                    emitter.completeWithError(ex);
               }
            },
            error -> {
                emitter.completeWithError(error);
            },
            emitter::complete
    );

    return emitter;
}

Test Class:测试类:

//A threaded dummy publisher to demonstrate async properties.
//Sends 2 responses with a 250ms pause in between.
protected static class MockPublisher implements Publisher<MyResponse> {
    @Override
    public void subscribe(Subscriber<? super MyResponse> subscriber) {
        new Thread() {
            @Override
            public void run() {
                try {
                    subscriber.onNext(response1);
                    Thread.sleep(250);
                    subscriber.onNext(response2);
                } catch (InterruptedException ex) {
                }
                subscriber.onComplete();
            }
        }.start();
    }
}

//Assume @Configuration that autowires the above mock publisher in the controller.

//Tests the output of the controller method.
@Test
public void testSseEmitter() throws Exception {
    String path = "http://localhost/mypath/";
    String expectedContent = "data:" + response1.toString() + "\n\n" +
                             "data:" + response2.toString() + "\n\n");

    //Trial-and-Error attempts at testing this SseEmitter mechanism have yielded the following:
    //- Returning an SseEmitter triggers 'asyncStarted'
    //- Calling 'asyncResult' forces the test to wait for the process to complete
    //- However, there is no actual 'asyncResult' to test.  Instead, the content is checked for the published data.
    mockMvc.perform(get(path).contentType(MediaType.ALL))
        .andExpect(status().isOk())
        .andExpect(request().asyncStarted())
        .andExpect(request().asyncResult(nullValue()))
        .andExpect(header().string("Content-Type", "text/event-stream"))
        .andExpect(content().string(expectedContent))
}

As noted in the comments, asyncResult() is called to ensure that the publisher finishes its work and sends both responses before the test completes.如评论中所述,调用 asyncResult() 以确保发布者完成其工作并在测试完成之前发送两个响应。 Without it, the content check fails due to only one response being present in the content.没有它,内容检查将失败,因为内容中只存在一个响应。 However there is no actual result to check, hence asyncResult is null.但是没有要检查的实际结果,因此 asyncResult 为空。

My specific question is whether there is a better, more precise way to force the test to wait for the async process to finish, rather than the klugie method here of waiting for a non-existent asyncResult.我的具体问题是是否有更好、更精确的方法来强制测试等待异步进程完成,而不是等待不存在的 asyncResult 的 klugie 方法。 My broader question is whether there are other libs or Spring methods that are better suited to this vs. these async functions.我更广泛的问题是,是否有其他库或 Spring 方法更适合于此与这些异步函数。 Thanks!谢谢!

This is a more general answer as it is meant to test an SseEmitter that will run forever, but will disconnect from SSE stream after a given timeout.这是一个更通用的答案,因为它旨在测试将永远运行但在给定超时后将与 SSE 流断开连接的 SseEmitter。

As for a different approach than MVC, as @ErinDrummond commented to the OP, you might want to investigate WebFlux.至于与 MVC 不同的方法,正如@ErinDrummond 对 OP 所评论的那样,您可能想要调查 WebFlux。

It is a minimal example.这是一个最小的例子。 One might want to expand with headers to the request, different matchers or maybe work on the stream output separately.人们可能想要扩展请求的标头、不同的匹配器或单独处理流输出。

It is setting a delayed thread for disconnecting from SSE Stream which will allow to perform assertions.它正在设置一个延迟线程以断开与 SSE Stream 的连接,这将允许执行断言。

@Autowired
MockMvc mockMvc;


@Test
public void testSseEmitter(){

    ScheduledExecutorService execService = Executors.newScheduledThreadPool(1);
    String streamUri = "/your-get-uri");
    long timeout = 500L;
    TimeUnit timeUnit = TimeUnit.MILLISECONDS;

    MvcResult result = mockMvc.perform(get(streamURI)
            .andExpect(request().asyncStarted()).andReturn();

    MockAsyncContext asyncContext = (MockAsyncContext) result.getRequest().getAsyncContext();
    execService.schedule(() -> {
        for (AsyncListener listener : asyncContext.getListeners())
            try {
                listener.onTimeout(null);
            } catch (IOException e) {
                e.printStackTrace();
            }
    }, timeout, timeUnit);

    result.getAsyncResult();

    // assertions, e.g. response body as string contains "xyz"
    mvc.perform(asyncDispatch(result)).andExpect(content().string(containsString("xyz")));
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM