[Day 18] Reactive Programming - Reactor Test(VirtualTime)

前言

接续上一篇介绍测试,之前也提到过Reactor提供VirtualTimeScheduler来让测试更方便,现在就来结合StepVerifierVirtualTimeScheduler看看会有甚麽火花。

withVirtualTime

测试使用StepVerifier传入publisher的方法,除了常用到的create来接收之外,withVirtualTime也是一个好方法,尤其是当你的情境是与时间有关的时候,从原始码可以看到,有别於create是直接传入一个publisherwithVirtualTime需要用Supplier包住publisher後再传入,原因是时间相关的operators通常是透过Schedulers.parallel(),如果要使用虚拟时间就要用VirtualTimeScheduler去取代掉原本的Scheduler,所以VirtualTimeScheduler必须在operators初始化之前就已经启动,也就是要延後operators初始化的时间,这句话基本上就等於lazy loading,所以为了让虚拟时间的机制成功,必须在Supplier内初始化Flux,而不是在其他的地方。

static <T> FirstStep<T> withVirtualTime(Supplier<? extends Publisher<? extends T>> scenarioSupplier) {
 return withVirtualTime(scenarioSupplier, Long.MAX_VALUE);
}

StepVerifier提供了两个方法让你可以控制时间增加

  1. thenAwait(Duration):暂停期望评估(expectation evaluation),停下来等相对就是时间增加(可以顺便反思人生不进则退,时光飞逝......)
  2. expectNoEvent(Duration):在传入的Duration期间内预期没有任何事情发生,相对也是一个时间推进的概念。要注意subscription也被视做一个event,如果一开始预期就没有event发生,建议使用expectSubscription().expectNoEvent(duration) ,要不然都会是错误。
    要注意在使用withVirtualTime的同时必须要用到上述的两种方法去增加时间,否则时间就将会停滞。
    https://ithelp.ithome.com.tw/upload/images/20211002/20141418osQCo94y78.png
图片来源:网路

从官方提供的范例可以看到,遇到delay建议就使用expectSubscription先起手,避免subscription这个事件导致expectNoEvent失败。透过VirtualTime推进了一天之後得到了一个资料0L,整个测试过程在真实世界只过了不到1秒。

StepVerifier.withVirtualTime(() -> Mono.delay(Duration.ofDays(1)))
    .expectSubscription()
    .expectNoEvent(Duration.ofDays(1))
    .expectNext(0L)
    .verifyComplete();

Question

在研究的时候找到一个stackoverflow的提问,提问者说,为何他的test如果不加上timeout的限制,就永远不会停止,而如果加上了则会有exception发生。

   StepVerifier.withVirtualTime(() -> 
            Flux.just(1, 2, 3, 4).delayElements(Duration.ofSeconds(1)))
            .expectSubscription()
            .expectNextCount(4)
            .expectComplete()
            .verify(Duration.ofSeconds(10));

//java.lang.AssertionError: VerifySubscribertimed out on reactor.core.publisher.FluxConcatMap$ConcatMapImmediate@66d1af89

如果你有认真阅读上面分享的内容并且吸收,你应该能看出问题点,为了避免暴雷,中间闲聊一下让大家思考,我看完找出问题後往下确认,发现回答的人居然是Simon Baslé!也就是Project Reactor的头头,这是不是有点像是你问了一个万有引力的问题,结果发现回答的人是牛顿。
https://ithelp.ithome.com.tw/upload/images/20211002/201414185pnPBOeyJ9.png

Answer

公布答案!答案就是他完全没有用到任何会增加时间的方法,顺利成为了让时间停滞的男/女人,在上面的内容有提到如何增加时间,往上找一个时钟的图案就能发现了,最後附上Project Reactor的头头的精采回答

You need to use .thenAwait(Duration), otherwise the (virtual) clock won't move, and the delay never happens. You can also use .expectNoEvent(Duration) after the expectSubscription().
For example:

@Test
public void test() {
  StepVerifier.withVirtualTime(() -> 
        Flux.just(1, 2, 3, 4).delayElements(Duration.ofSeconds(1)))
        .expectSubscription() //t == 0
//move the clock forward by 1s, and check nothing is emitted in the meantime
        .expectNoEvent(Duration.ofSeconds(1))
//so this effectively verifies the first value is delayed by 1s:
        .expectNext(1)
//and so on...
        .expectNoEvent(Duration.ofSeconds(1))
        .expectNext(2)
//or move the clock forward by 2s, allowing events to take place,
//and check last 2 values where delayed
        .thenAwait(Duration.ofSeconds(2))
        .expectNext(3, 4)
        .expectComplete()
//trigger the verification and check that in realtime it ran in under 200ms
        .verify(Duration.ofMillis(200));
}

资料来源


<<:  [Day18] Flutter with GetX binding (二 ) 元件与属性绑定

>>:  入门魔法 - ES6 箭头函式写法

Day27 Flutter—BLoC介绍

BLoC BLoC全称为 Business Logic Component,表示为业务逻辑组件。是独...

Day30 ( 游戏设计 ) 贪吃蛇

贪吃蛇 教学原文参考:贪吃蛇 这篇文章会介绍如何使用「阵列」、「函式」、「变数」、「点亮」、「[计次...

Day28 传播链程序实作

今天要来设计一种算法来查找从一个人到另一个人的病毒链,可以算是复习前面的for回圈,及swap的应用...

创建App一关於本App

创建App一关於本App(TeenMate) 为何选择在关於TeenMate的这一项功能作一天日记,...

【LeetCode】Monotonic Stack

monotonic :单调,递增或递减 这是一个看起来很简单的资料结构, 拥有 O(N) time ...