Spring boot 响应管道已订阅,但单元测试Mockito.verify()失败(未记录mock调用)

Spring boot 响应管道已订阅,但单元测试Mockito.verify()失败(未记录mock调用),spring-boot,mockito,reactive-programming,spring-webflux,Spring Boot,Mockito,Reactive Programming,Spring Webflux,我有这门课: @Slf4j @所需参数构造函数 @服务 公共类SyncTransactionService{ 私有最终SyncProducerService SyncProducerService;//卡夫卡制作人 私有最终耦合服务耦合服务;//db持久性服务 private final CouponUpdateMessageMapper映射器;//为Kafka生成消息dto的简单映射器 公共作废处理更改(列表更改){ 通量.可从可变(变化) .map(this::processAndSend)

我有这门课:

@Slf4j
@所需参数构造函数
@服务
公共类SyncTransactionService{
私有最终SyncProducerService SyncProducerService;//卡夫卡制作人
私有最终耦合服务耦合服务;//db持久性服务
private final CouponUpdateMessageMapper映射器;//为Kafka生成消息dto的简单映射器
公共作废处理更改(列表更改){
通量.可从可变(变化)
.map(this::processAndSend)
.doon错误(e->log.error(“无法将优惠券与更改同步。”,e))
.subscribeOn(Schedulers.elastic())
.subscribe();
}
专用单声道处理发送(更改){
返回Mono.fromCallable(()->change)
.doFirst(()->log.info(“保存或删除优惠券:{}”,change.getChanged()))
.map(this::saveOrDelete)
.thenReturn(mapper.map(更改))
.doOnSuccess(message->log.info(“发送消息:{}”,消息))
.doOnSuccess(syncProducerService::send);
}
专用Mono SaverDelete(更改){
if(change.getType()==DELETE)返回couponService.DELETE优惠券(change.getchange());
否则返回couponService.save优惠券(change.getChanged()).then();
}
}
这个测试:

@ExtendWith(MockitoExtension.class)
类SyncTransactionService测试{
@嘲弄
专用SyncProducerService SyncProducerService;
@嘲弄
私人耦合服务耦合服务;
@嘲弄
私有耦合更新消息映射器映射器;
@注射模拟
私人SyncTransactionService SyncTransactionService;
私人静态优惠券插入1;
私有静态优惠券更新ID2;
私有静态数据块deleteId3;
私有静态变化1;
私有静态变化2;
私有静态变化3;
@以前
私有静态无效准备数据(){
insertId1=DataHelper.优惠券();
updateId2=DataHelper.优惠券();
updateId2.setId(2);
deleteId3=DataHelper.优惠券();
deleteId3.setId(3);
change1=Change.builder().changed(insertId1).type(couponUpdate.INSERT).build();
change2=Change.builder().changed(updateId2).type(couponUpdate.UPDATE).build();
change3=Change.builder().changed(deleteId3).type(couponUpdate.DELETE).build();
}
@试验
void shouldProcessChanges(){
//给定
列表更改=列表(更改1、更改2、更改3);
when(couponService.save优惠券(insertId1)),然后return(Mono.just(insertId1));
when(couponService.save优惠券(updateId2))。然后返回(Mono.just(updateId2));
when(couponService.deleteToucon(deleteId3)).thenReturn(Mono.empty());
doNothing().when(syncProducerService).send(any());
doCallRealMethod().when(mapper).map(any());
//什么时候
syncTransactionService.processChanges(更改);
//然后
验证(couponService,times(2)).save优惠券(any());
验证(mapper,times(3)).map(any());
验证(couponService).delete优惠券(any());
验证(syncProducerService,次(3)).send(任意());
}
}
运行测试时,
Mockito.verify()
没有检测到与mock的任何交互,尽管代码中有
subscribe()


那么,在我的管道中会出现什么问题呢?

问题是,由于指定的调度程序,您的测试方法会异步运行。您应该从测试中的方法返回通量,然后使用
StepVerifier
或调用通量上的
collectList()
block()
方法来触发并等待执行

正如@Martin Tarjányi所说的那样,如果要测试一种反应式方法,并且它使用了
Schedulers.elastic()
,它将启动异步作业,您无法立即完成,因此我看不到任何交互

如果我坚持下去,我可以:

  • 等到它结束;(使用
    https://github.com/awaitility/awaitility
    lib或just
    Thread.sleep()
    ,例如:
    waitibility.waitAtMost(Duration.ofMillis(2000)).untilAsserted(()->{verify(…);});
  • 或者,返回管道并使用
    StepVerifier
    block()
    对其进行测试记住,对于流量,使用
    blockLast()
    获取全部
    blockFirst()
    仅发射第一个元素。
所以现在是这样的,

。。。
公共流量变化(列表变化){
返回流量。可从可从(更改)
.flatMap(this::processAndSend)
.doon错误(e->log.error(“无法将优惠券与更改同步。”,e))
.subscribeOn(Schedulers.elastic());//此处不订阅(),但返回它
}
...
和测试:

。。。
//什么时候
syncTransactionService.processChanges(changes).blockLast();//处理所有元素
...
我看到了日志,所有的互动都被记录下来了



如果我没有义务使用
Schedulers.elastic()
,我只需
subscribe()
,问题中的测试就可以了

是的,你是对的。在我的方法中,我不能<代码>订阅()>代码>测试以获得结果;如果要测试的话,我应该把管道还给你,照你说的做。我将在下面发布详细信息。谢谢那么,建议使用调度程序.elastic()?或者我只是让它订阅()而不使用任何
调度程序,这样我就可以订阅了?这取决于你的处理是什么;如果弹性调度程序执行某些阻塞IO操作,则应该使用弹性调度程序是的,那么我认为我必须使用弹性调度程序。