Spring boot 如何在SpringMVC中使用mockito、rxjava2和io调度器测试服务?

Spring boot 如何在SpringMVC中使用mockito、rxjava2和io调度器测试服务?,spring-boot,mockito,junit4,rx-java2,Spring Boot,Mockito,Junit4,Rx Java2,我正在尝试测试一种使用rxjava2、flatmap和io调度器的服务方法。测试似乎没有使用mockito调用任何被模拟的方法,尽管它似乎正在运行。可观察对象从不返回对象 如何使用多线程rxjava2代码测试spring服务 下面是我正在测试的服务中的方法 @Transactional(isolation = Isolation.READ_UNCOMMITTED) public io.reactivex.Observable<EntryStatistic> compute(final

我正在尝试测试一种使用rxjava2、flatmap和io调度器的服务方法。测试似乎没有使用mockito调用任何被模拟的方法,尽管它似乎正在运行。可观察对象从不返回对象

如何使用多线程rxjava2代码测试spring服务

下面是我正在测试的服务中的方法

@Transactional(isolation = Isolation.READ_UNCOMMITTED)
public io.reactivex.Observable<EntryStatistic> compute(final User user, final int startYear, final int endYear) {
    final GregorianCalendar calendarg = new GregorianCalendar();

    if (endYear < startYear)
        throw new IllegalArgumentException("endYear");
    if (endYear < 2003)
             throw new IllegalArgumentException("endYear");
    if (startYear < 2003)
        throw new IllegalArgumentException("startYear");

    return io.reactivex.Observable.range(startYear, endYear - startYear + 1)
            .flatMap(new Function<Integer, ObservableSource<EntryStatistic>>() {
                @Override
                public ObservableSource<EntryStatistic> apply(final Integer yr) throws Exception {
                    return io.reactivex.Observable.fromCallable(new Callable<EntryStatistic>() {
                        @Override
                        public EntryStatistic call() throws Exception {

                            log.debug("testing with year: " + yr + " user: " + user.getUsername() );
                            EntryStatistic es = entryStatisticRepository.findByUserAndYear(user, yr);
                            final long count = entryRepository.calendarCount(yr, user.getUsername());

                            if (es == null) {
                                log.trace("Creating new entry statistic");
                                es = new EntryStatistic();
                                es.setUser(user);
                            }

                            es.setCount(count);
                            es.setYear(yr);
                            es.setModified(calendarg.getTime());

                            log.trace("save and flush time");
                            return entryStatisticRepository.saveAndFlush(es);

                        }
                    });
                }
            }).subscribeOn(Schedulers.io());
}
@Transactional(隔离=隔离。读取\u未提交)
公共io.reactivex.Observable compute(最终用户、最终整数起始日期、最终整数结束日期){
最终GregorianCalendar calendarg=新的GregorianCalendar();
如果(年底<开始日期)
抛出新的非法辩论例外(“年终”);
如果(年底<2003年)
抛出新的非法辩论例外(“年终”);
如果(起始日期<2003年)
抛出新的IllegalArgumentException(“startYear”);
返回io.reactivex.Observable.range(起始日期、结束日期-起始日期+1)
.flatMap(新函数(){
@凌驾
公共ObserviceSource apply(最终整数yr)引发异常{
返回io.reactivex.Observable.fromCallable(newCallable()){
@凌驾
public EntryStatistic调用()引发异常{
调试(“测试年份:+yr+”用户:+user.getUsername());
EntryStatistic es=entryStatisticRepository.findByUserAndYear(用户,年);
最终长计数=entryRepository.calendarCount(yr,user.getUsername());
如果(es==null){
log.trace(“创建新条目统计”);
es=新入口统计();
es.setUser(用户);
}
es.setCount(count);
年份(年);
setModified(calendarg.getTime());
log.trace(“保存和刷新时间”);
返回entryStatisticRepository.saveAndFlush(es);
}
});
}
}).subscribeOn(Schedulers.io());
}
以下是测试代码:

@Test
public void computeSingle() {
    when(entryStatisticRepository.findByUserAndYear(user, TEST_YEAR + 1)).thenReturn(entryStatistic);
    when(user.getUsername()).thenReturn(TEST_USER);
    when(entryRepository.calendarCount(TEST_YEAR, TEST_USER)).thenReturn(1L);
    when(entryStatistic.getUser()).thenReturn(user);
    when(entryStatistic.getCount()).thenReturn(1L);
    when(entryStatistic.getYear()).thenReturn(TEST_YEAR);
    when(entryStatisticRepository.saveAndFlush(entryStatistic)).thenReturn(entryStatistic);

    TestObserver<EntryStatistic> testObserver = entryStatisticService.compute(user, TEST_YEAR, TEST_YEAR )
            .test();

    testObserver.awaitTerminalEvent();
    testObserver
        .assertNoErrors()
        .assertValue(new Predicate<EntryStatistic>() {
            @Override
            public boolean test(final EntryStatistic entryStatistic) throws Exception {
                return entryStatistic.getCount() == 1L ;
            }
        });

    verify(entryStatisticRepository, atLeastOnce()).findByUserAndYear(user, TEST_YEAR);
    verify(entryRepository, atLeastOnce()).calendarCount(TEST_YEAR, TEST_USER);
}
@测试
public void computeSingle(){
当(entryStatisticRepository.findByUserAndYear(用户,测试年+1))。然后返回(entryStatistic);
当(user.getUsername())。然后返回(TEST\u user);
when(entryRepository.calendarCount(TEST\u YEAR,TEST\u USER))。然后返回(1L);
当(entryStatistic.getUser())。然后返回(user);
when(entryStatistic.getCount())。然后返回(1L);
when(entryStatistic.getYear()),然后return(TEST\u YEAR);
当(entryStatisticRepository.saveAndFlush(entryStatistic)),然后返回(entryStatistic);
TestObserver TestObserver=entryStatisticService.compute(用户、测试年、测试年)
.test();
testObserver.awaitTerminalEvent();
测试观察者
.assertNoErrors()
.assertValue(新谓词(){
@凌驾
公共布尔测试(最终EntryStatistic EntryStatistic)引发异常{
return entryStatistic.getCount()==1L;
}
});
验证(entryStatisticRepository,至少一次()).findByUserAndYear(用户,测试年);
验证(entryRepository,atLeastOnce()).calendarCount(测试年,测试用户);
}
最后,我尝试将代码强制为单线程调度程序作为junit规则

public class TrampolineSchedulerRule implements TestRule {

@Override
public Statement apply(final Statement base, Description d) {
  return new Statement() {
    @Override
    public void evaluate() throws Throwable {
        RxJavaPlugins.setIoSchedulerHandler(new Function<Scheduler, Scheduler>() {
            @Override
            public Scheduler apply(final Scheduler scheduler) throws Exception {
                return Schedulers.trampoline();
            }
        });
        RxJavaPlugins.setComputationSchedulerHandler(new Function<Scheduler, Scheduler>() {
                         @Override
                         public Scheduler apply(final Scheduler scheduler) throws Exception {
                             return Schedulers.trampoline();
                         }
                     });
        RxJavaPlugins.setNewThreadSchedulerHandler(new Function<Scheduler, Scheduler>() {
                                      @Override
                                      public Scheduler apply(final Scheduler scheduler) throws Exception {
                                          return Schedulers.trampoline();
                                      }
                                  });


      try {
        base.evaluate();
      } finally {
        RxJavaPlugins.reset();
      }
    }
  };
}
}
公共类TrampolinesSchedulerRule实现TestRule{
@凌驾
公开声明适用(最终声明基础,说明d){
返回新语句(){
@凌驾
public void evaluate()可丢弃{
setIoSchedulerHandler(新函数(){
@凌驾
公共计划程序应用(最终计划程序)引发异常{
返回调度程序。蹦床();
}
});
setComputationSchedulerHandler(新函数(){
@凌驾
公共计划程序应用(最终计划程序)引发异常{
返回调度程序。蹦床();
}
});
RxJavaPlugins.setNewThreadSchedulerHandler(新函数(){
@凌驾
公共计划程序应用(最终计划程序)引发异常{
返回调度程序。蹦床();
}
});
试一试{
base.evaluate();
}最后{
RxJavaPlugins.reset();
}
}
};
}
}
问题是没有返回任何对象。我看到了错误

java.lang.AssertionError:没有值(闩锁=0,值=0,错误= 0,完成=1)


模拟的设置方式存在问题。这是一个嘲弄的电话

 when(entryStatisticRepository.saveAndFlush(entryStatistic)).thenReturn(entryStatistic);
期望
entryStatistic
对象被传递到
saveAndFlush
,但实际上新的
entryStatistic
对象被传递到
saveAndFlush
,并且不返回所需的模拟值。这意味着从
Callable
返回空值。由于
null
值不能在
rx-java2
中发出,因此
flatMap
不会发出任何值,并且您会收到没有值的即时完成

可通过移除+1 in来固定测试(因为范围仅会发出一个等于测试年的值)

一个有趣的问题是为什么
flatMap
不返回任何项目,并且成功完成了

Observable.range(0, 10).flatMap(i -> Observable.fromCallable(() -> null))
并以错误的形式完成

Observable.fromCallable(() -> null)
Observable.range(0, 10).flatMap(i -> Observable.error(new RuntimeException()))
并且也完成了,错误为

Observable.fromCallable(() -> null)
Observable.range(0, 10).flatMap(i -> Observable.error(new RuntimeException()))
我想这正是rx开发人员决定在
flatMap
中处理空值的方式,这里我们正在与一些