RxJava测试:如何等待所有后台任务完成

时间:2016-04-29 10:36:09

标签: java spring-boot rx-java spring-cloud

TLDR:我在RxJava Observables中进行后台处理,我正在进行集成测试,我希望能够独立等待该处理完成,以确保从一次测试开始的后台处理不会干扰另一次测试测试

简化,我有一个@RequestMapping方法执行以下操作:

  • 在数据库中插入数据
  • 启动对该数据的异步处理(通过Feign,数据库更新进行http调用)
  • 不返回任何内容(HttpStatus.NO_CONTENT

此异步处理以前是使用ThreadPoolTaskExecutor完成的。我们将转换到RxJava并希望删除此ThreadPoolTask​​Executor并使用RxJava进行后台处理。

我试图这样做的时候非常天真:

Observable
    .defer(() -> Observable.just(call to long blocking method) 
    .subscribeOn(Schedulers.io())
    .subscribe();

最终目标当然是,一步一步,进入"调用长阻塞方法"并一直使用Observable。

在此之前,我想先让我的集成测试工作。我通过对映射执行RestTemplate调试来测试它。由于大多数工作是异步的,我的调用返回非常快。现在我想找到一种方法来等待异步处理完成(以确保它不与另一个测试冲突)。

在RxJava之前,我只计算ThreadPoolTask​​Executor中的任务,并等到它达到0。

我怎么能用RxJava做到这一点?

我尝试了什么:

  • 我尝试使用RxJavaSchedulersHook立即调度所有调度程序:这导致某处某种阻塞,代码执行在我的Feign调用之前停止(Feign在引擎盖下使用RxJava)
  • 我尝试使用Rx RxJavaObservableExecutionHook计算任务:我尝试保留订阅,并在isSubcribed = false时删除它们,但这根本不起作用(许多订阅者,计数永远不会下降)< / LI>
  • 我试图将一个observeOn(immediate())放在真实的生产代码中。这似乎有效,我可以为运行时/测试阶段注入正确的调度程序,但我并不真正热衷于在我的实际生产代码中将代码用于测试目的。

我可能非常错误,或者过于复杂的事情,所以不要犹豫,纠正我的理由!

3 个答案:

答案 0 :(得分:4)

如何退回HttpStatus.NO_CONTENT

@RequestMapping(value = "/")
public HttpStatus home() {
    Observable.defer(() -> Observable.just(longMethod())
              .subscribeOn(Schedulers.io())
              .subscribe();
    return HttpStatus.NO_CONTENT;
}

在此表单中,您无法知道longMethod何时完成。

如果您想知道所有异步作业何时完成,您可以使用Spring HttpStatus.NO_CONTENT或使用DefferedResult

在所有作业完成后返回TestSubscriber

PS:如果您愿意,可以使用Observable.fromCallable(() -> longMethod());代替Observable.defer(() -> Observable.just(longMethod());

使用DefferedResult

@RequestMapping(value = "/")
public DeferredResult<HttpStatus> index() {
    DeferredResult<HttpStatus> deferredResult = new DeferredResult<HttpStatus>();
    Observable.fromCallable(() -> longMethod())
            .subscribeOn(Schedulers.io())
            .subscribe(value -> {}, e -> deferredResult.setErrorResult(e.getMessage()), () -> deferredResult.setResult(HttpStatus.NO_CONTENT))
    return deferredResult;
}

像这样,如果你调用你的方法,只有当你的observable完成时才会得到你的结果(所以,当longMethod完成时)

使用TestSubscriber

当你要求他等待/检查你的Observable的完成时,你必须注射一个TestSubscriber

@RequestMapping(value = "/")
public HttpStatus home() {
    Observable.defer(() -> Observable.just(longMethod())
              .subscribeOn(Schedulers.io())
              .subscribe(subscriber); // you'll have to inject this subscriber in your test
    return HttpStatus.NO_CONTENT;
}

并在你的测试中:

 TestSubscriber subscriber = new TestSubscriber(); // you'll have to inject it into your controller
 // ....
 controller.home();
 subscriber.awaitTerminalEvent();
 subscriber.assertCompleted(); // check that no error occurred

答案 1 :(得分:1)

您可以使用ExecutorServiceAdapter从Spring ThreadPoolTaskExecutor桥接到RX中的ExecutorService,然后执行与之前相同的操作。

答案 2 :(得分:0)

几个月后的比赛:我的建议只是“不要那样做”。 RxJava并不适合这种工作。没有太多详细说明在后台运行大量“松散”Observable是不合适的:根据您的请求量,您很容易陷入队列和内存问题,更重要的是所有已调度和正在运行的任务会发生什么如果网络服务器崩溃了?你怎么重新启动它?

Spring提供了更好的替代方案imho:Spring BatchSpring Cloud Task,与Spring Cloud Stream进行消息传递,所以不要像我那样做,只需使用正确的工具来做正确的工作。< / p>

现在如果你真的想走坏路线:

  • 返回SseEmmitter并仅使用消费者服务中SSE的第一个事件,并使用测试中的所有事件
  • 创建一个RxJava lift运算符,该运算符包含(在call方法中)具有waitForCompletion方法的父订阅服务器中的订阅服务器。你如何做等待取决于你(例如CountDownLatch)。该订阅者将被添加到同步列表中(并在完成后从中删除),并且在您的测试中,您可以迭代列表并在列表的每个项目上调用waitForCompletion。它并没有那么复杂,我让它工作,但请不要这样做!