随着响应式编程RxJava这几年的火热,大家在项目中也会常常使用。RxJava提供了大量的操作符,让我们的代码显得更简洁,对于线程的切换也更加自如。那么当我们写单元测试时,如何方便的测试RxJava呢?这就是本篇的内容。
首先添加一下依赖
//RxJava
compile 'io.reactivex.rxjava2:rxjava:2.1.7'
RxJava2提供了我们TestObserver
,它可以记录事件并允许对它们进行断言。
@Test
public void testObserver() {
TestObserver testObserver = TestObserver.create();
testObserver.onNext(1);
testObserver.onNext(2);
//断言值是否相等
testObserver.assertValues(1, 2);
testObserver.onComplete();
//断言是否完成
testObserver.assertComplete();
}
在上面的代码中,我们创建了TestObserver
,并且依次发射了1和2两个数字。因为可以记录我们的操作事件并且断言。所以我们很快的测试了我们想验证的结果。
同样的RxJava内置了TestSubscriber
类,它可以记录事件并允许对它作出断言,那么使用TestSubscriber
就可以去便捷的验证Observable
。下面的内容很简单,我也写了对应的注释。只要你会使用RxJava都可以快速的理解。
@Test
public void testJust() {
TestSubscriber testSubscriber = new TestSubscriber<>();
//依次发射A,B,C
Flowable.just("A", "B", "C").subscribe(testSubscriber);
//断言值是否不存在
testSubscriber.assertNever("D");
//断言值是否相等
testSubscriber.assertValues("A", "B", "C");
//断言值的数量是否相等
testSubscriber.assertValueCount(3);
//断言是否结束
testSubscriber.assertTerminated();
}
@Test
public void testFrom() {
TestSubscriber testSubscriber = new TestSubscriber<>();
//依次发射list中的数字
Flowable.fromIterable(Arrays.asList(1, 2)).subscribe(testSubscriber);
testSubscriber.assertValues(1, 2);
testSubscriber.assertValueCount(2);
testSubscriber.assertTerminated();
}
@Test
public void testRange() {
TestSubscriber testSubscriber = new TestSubscriber<>();
//从3开始发射3个连续的int
Flowable.range(3, 3).subscribe(testSubscriber);
testSubscriber.assertValues(3, 4, 5);
testSubscriber.assertValueCount(3);
testSubscriber.assertTerminated();
}
@Test
public void testRepeat() {
TestSubscriber testSubscriber = new TestSubscriber<>();
Flowable.fromIterable(Arrays.asList(1, 2))
.repeat(2) //重复发送2次
.subscribe(testSubscriber);
testSubscriber.assertValues(1, 2, 1, 2);
testSubscriber.assertValueCount(4);
testSubscriber.assertTerminated();
}
@Test
public void testBuffer() {
TestSubscriber> testSubscriber = new TestSubscriber<>();
//缓冲2个发射一次
Flowable.just("A", "B", "C", "D")
.buffer(2)
.subscribe(testSubscriber);
testSubscriber.assertResult(Arrays.asList("A", "B"), Arrays.asList("C", "D"));
testSubscriber.assertValueCount(2);
testSubscriber.assertTerminated();
}
@Test
public void testError() {
TestSubscriber testSubscriber = new TestSubscriber();
Exception exception = new RuntimeException("error");
Flowable.error(exception).subscribe(testSubscriber);
//断言错误是否一致
testSubscriber.assertError(exception);
//断言错误信息是否一致
testSubscriber.assertErrorMessage("error");
}
在测试有关时间的操作符时,可能我们的事件还没有执行完,因此无法得到预期的输出结果,断言就无效了。当然我们可以使用上一篇说到的将异步转为同步思路,如下:
public class RxJavaRule implements TestRule {
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
RxJavaPlugins.setIoSchedulerHandler(new Function() {
@Override
public Scheduler apply(Scheduler scheduler) throws Exception {
return Schedulers.trampoline();
}
});
RxJavaPlugins.setNewThreadSchedulerHandler(new Function() {
@Override
public Scheduler apply(Scheduler scheduler) throws Exception {
return Schedulers.trampoline();
}
});
RxJavaPlugins.setComputationSchedulerHandler(new Function() {
@Override
public Scheduler apply(Scheduler scheduler) throws Exception {
return Schedulers.trampoline();
}
});
try {
base.evaluate();
} finally {
RxJavaPlugins.reset();
}
}
};
}
}
测试代码:
@Rule
public RxJavaRule rule = new RxJavaRule();
@Test
public void testInterval() {
TestSubscriber testSubscriber = new TestSubscriber<>();
//隔1秒发射一次,一共10次
Flowable.interval(1, TimeUnit.SECONDS)
.take(10)
.subscribe(testSubscriber);
testSubscriber.assertValueCount(10);
testSubscriber.assertTerminated();
}
但是这样,我们的测试方法就要等待10s,也就是等待方法执行完毕。这完全都没有了单元测试快捷的优点了。所以我来介绍一个更方便的方法。
RxJava 提供了 TestScheduler
,通过这个调度器可以实现对时间的操控。那么我们的测试代码就变成了:
@Test
public void testInterval() {
TestScheduler mTestScheduler = new TestScheduler();
TestSubscriber testSubscriber = new TestSubscriber<>();
//隔1秒发射一次,一共10次
Flowable.interval(1, TimeUnit.SECONDS, mTestScheduler)
.take(10)
.subscribe(testSubscriber);
//时间经过3秒
mTestScheduler.advanceTimeBy(3, TimeUnit.SECONDS);
testSubscriber.assertValues(0L, 1L, 2L);
testSubscriber.assertValueCount(3);
testSubscriber.assertNotTerminated();
//时间再经过2秒
mTestScheduler.advanceTimeBy(2, TimeUnit.SECONDS);
testSubscriber.assertValues(0L, 1L, 2L, 3L ,4L);
testSubscriber.assertValueCount(5);
testSubscriber.assertNotTerminated();
//时间到10秒
mTestScheduler.advanceTimeTo(10, TimeUnit.SECONDS);
testSubscriber.assertValueCount(10);
testSubscriber.assertTerminated();
}
是不是变得很方便,我们不仅不需要等待,还可以操控时间,可以到达任意的时间节点。
当然我们也可以利用@Rule将这个封装一下,便于使用:
public class RxJavaTestSchedulerRule implements TestRule {
private final TestScheduler mTestScheduler = new TestScheduler();
public TestScheduler getTestScheduler() {
return mTestScheduler;
}
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
RxJavaPlugins.setIoSchedulerHandler(new Function() {
@Override
public Scheduler apply(Scheduler scheduler) throws Exception {
return mTestScheduler;
}
});
RxJavaPlugins.setNewThreadSchedulerHandler(new Function() {
@Override
public Scheduler apply(Scheduler scheduler) throws Exception {
return mTestScheduler;
}
});
RxJavaPlugins.setComputationSchedulerHandler(new Function() {
@Override
public Scheduler apply(Scheduler scheduler) throws Exception {
return mTestScheduler;
}
});
RxAndroidPlugins.setMainThreadSchedulerHandler(new Function() {
@Override
public Scheduler apply(Scheduler scheduler) throws Exception {
return mTestScheduler;
}
});
try {
base.evaluate();
} finally {
RxJavaPlugins.reset();
RxAndroidPlugins.reset();
}
}
};
}
}
@Test
public void testTimer() {
TestScheduler mTestScheduler = new TestScheduler();
TestSubscriber testSubscriber = new TestSubscriber<>();
//延时5秒发射
Flowable.timer(5, TimeUnit.SECONDS, mTestScheduler)
.subscribe(testSubscriber);
//时间到5秒
mTestScheduler.advanceTimeTo(5, TimeUnit.SECONDS);
testSubscriber.assertValueCount(1);
testSubscriber.assertTerminated();
}
我们用获取验证码这个场景来检验今天的内容。点击发送验证码按钮,按钮不能再点击,倒计时120s,时间过了后,按钮就可以点击了。很简单的一个场景。
界面:
代码如下:
public class LoginActivity extends AppCompatActivity {
private Disposable mDisposable;
private TextView mTvSendIdentify;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mTvSendIdentify = (TextView) this.findViewById(R.id.tv_send_identify);
mTvSendIdentify.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
getIdentify();
}
});
}
private void getIdentify() {
mTvSendIdentify.setEnabled(false);
// interval隔一秒发一次,到120结束
mDisposable = Observable
.interval(1, TimeUnit.SECONDS, AndroidSchedulers.mainThread())
.take(120)
.subscribeWith(new DisposableObserver() {
@Override
public void onComplete() {
mTvSendIdentify.setText(R.string.login_send_identify);
mTvSendIdentify.setEnabled(true);
}
@Override
public void onError(Throwable e) {}
@Override
public void onNext(Long aLong) {
mTvSendIdentify.setText(TextUtils.concat(String.valueOf(Math.abs(aLong - 120)), "秒后重试"));
}
});
}
@Override
public void onDestroy() {
super.onDestroy();
if (mDisposable != null){
mDisposable.dispose();
}
}
}
测试代码:
@RunWith(RobolectricTestRunner.class)
@Config(constants = BuildConfig.class, sdk = 23)
public class LoginActivityTest {
private LoginActivity loginActivity;
private TextView mTvSendIdentify;
@Rule
public RxJavaTestSchedulerRule rule = new RxJavaTestSchedulerRule();
@Before
public void setUp(){
loginActivity = Robolectric.setupActivity(LoginActivity.class);
mTvSendIdentify = (TextView) loginActivity.findViewById(R.id.tv_send_identify);
}
@Test
public void testGetIdentify() throws Exception {
Application application = RuntimeEnvironment.application;
Assert.assertEquals(mTvSendIdentify.getText().toString(),
application.getString(R.string.login_send_identify));
// 触发按钮点击
mTvSendIdentify.performClick();
// 时间到10秒
rule.getTestScheduler().advanceTimeTo(10, TimeUnit.SECONDS);
assertEquals(mTvSendIdentify.isEnabled(), false);
assertEquals(mTvSendIdentify.getText().toString(), "111秒后重试");
// 时间到120秒
rule.getTestScheduler().advanceTimeTo(120, TimeUnit.SECONDS);
assertEquals(mTvSendIdentify.getText().toString(),
application.getString(R.string.login_send_identify));
assertEquals(mTvSendIdentify.isEnabled(), true);
}
}
如果你这样做单元测试,是不是就不用傻傻的安装后再点击按钮,等待着时间流逝了。
本篇所有代码已上传至Github。希望大家多多点赞支持!