Java 创建等待所有Scheduler.io()完成的Mockito测试用例

Java 创建等待所有Scheduler.io()完成的Mockito测试用例,java,mockito,rx-java,Java,Mockito,Rx Java,我有一个bug,它只有在并行运行时才会显现出来,例如,在Schedulers.io()上。修复方法很简单-将flatMapCompletable替换为concatmappletable,以明确强制顺序执行 但是,我的bug复制单元测试调用verify()太早了,因为它没有等待完成 重要提示:被测函数返回void,因此我无法直接附加到我正在测试的可观察对象。我必须启动该函数,然后等待所有线程完成,然后检查结果 如果我用Schedulers.trampoline()替换Schedulers.io()

我有一个bug,它只有在并行运行时才会显现出来,例如,在
Schedulers.io()
上。修复方法很简单-将
flatMapCompletable
替换为
concatmappletable
,以明确强制顺序执行

但是,我的bug复制单元测试调用
verify()
太早了,因为它没有等待完成

重要提示:被测函数返回
void
,因此我无法直接附加到我正在测试的可观察对象。我必须启动该函数,然后等待所有线程完成,然后检查结果

如果我用
Schedulers.trampoline()
替换
Schedulers.io()
,测试将一直执行到最后,但它无法再现问题,因为代码在同一线程上执行,因此不会导致特定于并行性的错误

放置
睡眠(3000)
验证之前
修复了测试,但这是一个丑陋的解决方案

在我的测试中调用
verify
之前,如何正确地等待所有
Schedulers.io()
线程完成它们的工作

下面是一个被测试代码的简化示例,注释掉了修复程序,因此测试失败,测试方法也失败了。在这个简化版本中,它实际上对Mockito没有任何作用:

List<Integer> responseCompletions = new ArrayList<>();

private void methodUnderTest()
{
    Completable longCompletable = Completable.fromRunnable(() -> {
        System.out.println("Was requested for id 1");
        try {
            sleep(2000);
        } catch (InterruptedException e) { }
        System.out.println("Returning result for id 1 after long sleep");
        responseCompletions.add(1);
    })
    // must run on io() ! not main thread! otherwise test succeeds when it should actually fail
    .subscribeOn(Schedulers.io()).observeOn(Schedulers.io());

    Completable shortCompletable = Completable.fromRunnable(() -> {
        System.out.println("Was requested for id 2");
        try {
            sleep(100);
        } catch (InterruptedException e) { }
        System.out.println("Returning result for id 2 after short sleep");
        responseCompletions.add(2);
    })
    // must run on io() ! not main thread! otherwise test succeeds when it should actually fail
    .subscribeOn(Schedulers.io()).observeOn(Schedulers.io());

    List<String> list = Arrays.asList("1", "2");

    Observable.fromIterable(list)
            //.concatMapCompletable(item -> {   // <- this is the fix for the code under test
            .flatMapCompletable(item -> {

                System.out.println("Making completable for " + item);

                if (item == "1")
                    return longCompletable;
                else if (item == "2")
                    return shortCompletable;

                return Completable.complete();
            })
            .subscribeOn(Schedulers.io())
            .observeOn(Schedulers.io())
            .subscribe(() -> {
                        System.out.println("list done");
                    },
                    throwable -> {});
}

@Test
public void cleanedUpCompletablesSequenceExperiments() throws Exception {

    methodUnderTest();

    // would like to get rid of this ugly sleep
    // - there should be a better way to wait for waiting for it to complete?
    sleep(3000);

    assertEquals (1, (int)(responseCompletions.get(0)));
    assertEquals (2, (int)(responseCompletions.get(1)));
}
List responseCompletions=new ArrayList();
私有void methodUnderTest()
{
Completable longCompletable=Completable.fromRunnable(()->{
System.out.println(“请求id为1”);
试一试{
睡眠(2000年);
}捕获(中断异常e){}
System.out.println(“长时间睡眠后返回id 1的结果”);
响应完成。添加(1);
})
//必须在io()上运行!不是主线程!否则测试将在实际失败时成功
.subscribeOn(Schedulers.io()).observeOn(Schedulers.io());
Completable shortCompletable=Completable.fromRunnable(()->{
System.out.println(“请求id为2”);
试一试{
睡眠(100);
}捕获(中断异常e){}
System.out.println(“短暂睡眠后返回id 2的结果”);
响应完成。添加(2);
})
//必须在io()上运行!不是主线程!否则测试将在实际失败时成功
.subscribeOn(Schedulers.io()).observeOn(Schedulers.io());
List=Arrays.asList(“1”、“2”);
可观察的。从可观察的(列表)
//.concatMapCompletable(项目->{//{
System.out.println(“使“+项可完成”);
如果(项目==“1”)
返回可完成;
否则,如果(项目==“2”)
返回短可完成;
返回Completable.complete();
})
.subscribeOn(Schedulers.io())
.observeOn(Schedulers.io())
.订阅(()->{
System.out.println(“列表完成”);
},
可丢弃->{});
}
@试验
public void cleanedUpCompletablesSequenceExperiments()引发异常{
方法测试();
//想摆脱这种难看的睡眠吗
//-应该有更好的方法等待它完成吗?
睡眠(3000);
assertEquals(1,(int)(responseCompletions.get(0));
assertEquals(2,(int)(responseCompletions.get(1));
}
您可以在
方法测试()中使用:

在其开头添加:

private void methodUnderTest()引发InterruptedException{
最终CountDownLatch completables=新的CountDownLatch(2);
Completable
s末尾添加:

响应完成。添加(1);
completables.countDown();
响应完成。添加(2);
completables.countDown();
在方法末尾添加:

throwable->{});
completables.wait();
这修复了删除睡眠(3000)时的
索引自动边界异常
,但测试仍然失败:

java.lang.AssertionError:应为:但为:
连同:

assertTrue(“List responseCompletions不包含1.”,responseCompletions.contains(1));
assertTrue(“List responseCompletions不包含2.”,responseCompletions.contains(2));

正如我在对你的问题的评论中提到的,测试成功了。

你能编辑你的问题以包括单元测试的某种MCVE以及你如何设置模拟和RX订阅吗?@Progman实际的代码有点复杂,但我设法创建了一个经过清理的简化版本,它甚至不再使用模拟了问题是相同的-丑陋的
睡眠
等待
io()
线程完成其工作。@JustAMartin使用
subscribe()是一个选项吗
重载方法,该方法在可观察对象完成时接受一个
操作
对象?这样,您将知道它已完成并可以对其作出反应。这实际上更多的是API设计问题-您正在尝试测试一个异步并返回void的函数-客户端无法判断它何时完成。您应该重构函数,使其返回一个
可完成的
。这样,您的测试就可以订阅该函数,并在函数完成时得到通知。@GeroldBrosersRestoresmonica测试中的代码最初使用
flatMapCompletable
Schedulers.io()
,这使得它可以并行执行Completable。对应于测试中的业务逻辑案例,这段代码不应该并行执行,因此它应该使用
concatMapCompletable
。这就是为什么我首先创建了一个使用原始错误并行代码的测试,但失败了,然后