1、使用StepVerifier测试响应式流
由于响应式框架不同于常规框架,测试复杂度相对困难。出于测试目的,Reactor 提供了额外的 reactor-test 模块,该模块提供了 StepVerifier。StepVerifier 提供了一个流式 API,用于为任何 Publisher 构建验证流程。
1.1、StepVerifier 要点
验证 Publisher 主要有两种方法。第一种是 StepVerifier.<T>create(Publisher<T> source)。使用此技术构建的测试如下所示:
@Test
public void test1() {
StepVerifier
.create(Flux.just("bar", "foo"))
//声明发出的事件:当前为订阅事件
.expectSubscription()
//声明订阅收到的第一个元素
.expectNext("foo")
//第二个元素
.expectNext("bar")
//声明当前流信号终止完成
.expectComplete()
.log()
//对上述声明进行验证
.verify();
}
在此示例中,Publisher 应生成两个特定元素,后续操作将验证特定元素是否已传递给最终订阅者。该类提供的构建器技术可以定义验证过程中事件发生的顺序。根据前面的代码,第一个发出的事件必须是与订阅相关的事件,紧跟其后的事件必须是 foo 和 bar 字符串。最后, StepVerifier#expectCompletion 定义终止信号的存在。
在此例中,必须是 Subscriber#onComplete 的调用,或者成功完成给定的 Flux。要执行验证,或者说对创建流进行订阅,就必须调用 .verify() 方法。verify() 是一个阻塞调用,它阻塞执行,直到流发出所有预期的事件。
通过使用这种简单的技术,可以使用可计数的元素和事件来验证 Publisher。但是,用大量元素来验证流程是很困难的。如果检查的是该发布者已发出元素是否达到特定数量,可以使用 .expectNextCount() 。如下代码:
@Test
public void test2() {
StepVerifier
//创建一个响应式序列
.create(Flux.range(0, 100))
//声明处理的是订阅事件
.expectSubscription()
//期望下一个元素与指定的元素符合
.expectNext(0)
//声明从上一个元素开始,期望收到指定个数的元素
.expectNextCount(98)
//期望收到的下一个元素与指定的元素符合
.expectNext(99)
//期望收到完成信号
.expectComplete()
//执行阻塞验证
.verify();
}
尽管 .expectNextCount() 方法解决了一部分问题,但在某些情况下,仅仅检查发出元素的数量是不够的。例如,在验证负责按特定规则过滤或选择元素的代码时,检查所有发出的项是否与过滤规则匹配非常重要。为此,StepVerifier 可以使用 Java Hamcrest 等工具立即记录发出的数据及其验证。如下代码:
package cn.blnp.net.reactboot;
import cn.blnp.net.reactboot.entity.BookEntity;
import org.junit.jupiter.api.Test;
import org.reactivestreams.Publisher;
import reactor.core.publisher.Flux;
import reactor.test.StepVerifier;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.*;
import java.util.ArrayList;
/**
* <p></p>
*
* @author lyb [email protected]
* @version 1.0
* @since 2025/1/16 9:51
*/
public class DemoTest {
@Test
public void test3() {
//模拟接口查询取得数据
Publisher<BookEntity> books = findAllBooks();
//验证测试当前流结果
StepVerifier
.create(books)
.expectSubscription()
.recordWith(ArrayList::new)
.expectNextCount(1)
.consumeRecordedWith(book -> assertThat(
//待判定对象
book,
//比对规则
everyItem(
//判定传入对象是否有指定名字的属性
hasProperty("title",
//当上述判定有指定属性时,则验证属性值是否与给定的一致
equalTo("The Martian"))