本系列文章索引《响应式Spring的道法术器》
前情提要:Reactor 3快速上手 | 响应式流规范
本文测试源码java
在很是重视DevOps的今天,以及一些奉行TDD的团队中,自动化测试是保证代码质量的重要手段。要进行Reactor的测试,首先要确保添加reactor-test
依赖。react
reactor-test 用 Maven 配置 <dependencies>git
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<version>3.1.4.RELEASE</version>
<scope>test</scope>
</dependency>githubreactor-test 用 Gradle 配置app
dependencies {
testCompile 'io.projectreactor:reactor-test:3.1.4.RELEASE'
}ide
1.3.2.3节初步介绍了关于StepVerifier
的用法。举个例子回忆一下:测试
@Test public void testAppendBoomError() { Flux<String> source = Flux.just("foo", "bar"); StepVerifier.create( appendBoomError(source)) .expectNext("foo") .expectNext("bar") .expectErrorMessage("boom") .verify(); }
咱们一般使用create
方法建立基于Flux或Mono的StepVerifier
,而后就能够进行如下测试:线程
expectNext(T...)
或expectNextCount(long)
。`consumeNextWith(Consumer<T>)
。thenAwait(Duration)
和then(Runnable)
。对于终止事件,相应的指望方法(如expectComplete()
、expectError()
,及其全部的变体方法) 使用以后就不能再继续增长别的指望方法了。最后你只能对 StepVerifier 进行一些额外的配置并 触发校验(一般调用verify()
及其变体方法)。code
从StepVerifier
内部实现来看,它订阅了待测试的 Flux 或 Mono,而后将序列中的每一个信号与测试 场景的指望进行比对。若是匹配的话,测试成功。若是有不匹配的状况,则抛出AssertionError
异常。blog
响应式流是一种基于时间的数据流。许多时候,待测试的数据流存在延迟,从而持续一段时间。若是这种场景比较多的话,那么会致使自动化测试运行时间较长。所以StepVerifier
提供了能够操做“虚拟时间”的测试方式,这时候须要使用StepVerifier.withVirtualTime
来构造。
为了提升 StepVerifier 正常起做用的几率,它通常不接收一个简单的 Flux 做为输入,而是接收 一个Supplier
,从而能够在配置好订阅者以后 “懒建立”待测试的 flux,如:
StepVerifier.withVirtualTime(() -> Mono.delay(Duration.ofDays(1))) //... 继续追加指望方法
有两种处理时间的指望方法,不管是否配置虚拟时间都是可用的:
thenAwait(Duration)
会暂停校验步骤(容许信号延迟发出)。expectNoEvent(Duration)
一样让序列持续必定的时间,期间若是有任何信号发出则测试失败。在普通的测试中,两个方法都会基于给定的持续时间暂停线程的执行。而若是是在虚拟时间模式下就相应地使用虚拟时间。
StepVerifier.withVirtualTime(() -> Mono.delay(Duration.ofDays(1))) .expectSubscription() // 1 .expectNoEvent(Duration.ofDays(1)) // 2 .expectNext(0L) .verifyComplete(); // 3
expectSubscription().expectNoEvent(duration)
来代替;verify
或变体方法最终会返回一个Duration
,这是实际的测试时长。可见,withVirtualTime使咱们不用实际等1天来完成测试了。
虚拟时间的功能是如何实现的呢?StepVerifier.withVirtualTime
会在Reactor的调度器工厂方法中插入一个自定义的调度器VirtualTimeScheduler
来代替默认调度器(那些基于时间的操做符一般默认使用Schedulers.parallel()
调度器)。
一般状况下,使用StepVerifier
的expect*
就能够搞定多数的测试场景了。可是,它也有机关用尽的时候,好比下边这个特殊的例子:
private Mono<String> executeCommand(String command) { // 基于command执行一些操做,执行完成后返回Mono<String> } public Mono<Void> processOrFallback(Mono<String> commandSource, Mono<Void> doWhenEmpty) { return commandSource .flatMap(command -> executeCommand(command).then()) // 1 .switchIfEmpty(doWhenEmpty); // 2 }
then()
会忽略全部的元素,只保留完成信号,因此返回值为Mono<Void>;1和2都是Mono<Void>
,这时候就比较难判断processOfFallback
中具体执行了哪条路径。这时候能够用log()
或doOn*()
等方法来观察,但这“在非绿即红”的单测中不起做用。或者在某个路径加入标识状态的值,并经过判断状态值是否正确来肯定,但这就须要修改被测试的processOfFallback
的代码了。
Reactor版本 3.1.0 以后咱们可使用PublisherProbe
来作相似场景的验证。以下:
@Test public void testWithPublisherProbe() { PublisherProbe<Void> probe = PublisherProbe.empty(); // 1 StepVerifier.create(processOrFallback(Mono.empty(), probe.mono())) // 2 .verifyComplete(); probe.assertWasSubscribed(); // 3 probe.assertWasRequested(); // 4 probe.assertWasNotCancelled(); // 5 }
TestPublisher
本质上是一个Publisher
,不过使用它能更加“自由奔放”地发出各类元素,以便进行各类场景的测试。
1)“自由”地发出元素
咱们能够用它提供的方法发出各类信号:
next(T)
以及 next(T, T...)
发出 1-n 个 onNext 信号。emit(T...)
起一样做用,而且会执行 complete()。complete()
会发出终止信号 onComplete。error(Throwable)
会发出终止信号 onError。好比:
@Test public void testWithTestPublisher() { TestPublisher<Integer> testPublisher = TestPublisher.<Integer>create().emit(1, 2, 3); StepVerifier.create(testPublisher.flux().map(i -> i * i)) .expectNext(1, 4, 9) .expectComplete(); }
2)“奔放”地发出元素
使用create
工厂方法就能够获得一个正常的TestPublisher
。而使用createNonCompliant
工厂方法能够建立一个“不正常”的TestPublisher
。后者须要传入由TestPublisher.Violation
枚举指定的一组选项,这些选项可用于告诉 publisher 忽略哪些问题。枚举值有:
REQUEST_OVERFLOW
: 容许 next 在请求不足的时候也能够调用,而不会触发 IllegalStateException。ALLOW_NULL
: 容许 next 可以发出一个 null 值而不会触发 NullPointerException。CLEANUP_ON_TERMINATE
: 能够重复屡次发出终止信号,包括 complete()、error() 和 emit()。不过这个功能可能更多地是给Reactor项目开发者自己使用的,好比当他们开发了一个新的操做符,能够用这种方式来测试这个操做符是否知足响应式流的规范。
3)TestPublisher也是个PublisherProbe
更赞的是,TestPublisher
实现了PublisherProbe
接口,意味着咱们还可使用它提供的assert*
方法来跟踪其内部的订阅和执行状态。