Android单元测试(六):RxJava测试

随着响应式编程RxJava这几年的火热,大家在项目中也会常常使用。RxJava提供了大量的操作符,让我们的代码显得更简洁,对于线程的切换也更加自如。那么当我们写单元测试时,如何方便的测试RxJava呢?这就是本篇的内容。

1.一个简单的测试

首先添加一下依赖

    //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两个数字。因为可以记录我们的操作事件并且断言。所以我们很快的测试了我们想验证的结果。

2.一些常用操作符的测试

同样的RxJava内置了TestSubscriber类,它可以记录事件并允许对它作出断言,那么使用TestSubscriber就可以去便捷的验证Observable。下面的内容很简单,我也写了对应的注释。只要你会使用RxJava都可以快速的理解。

1.just

    @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();
    }

2.from

    @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();
    }

3.range

    @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();
    }

4.repeat

    @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();
    }

5.buffer

    @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();
    }

6.error

     @Test
    public void testError() {
        TestSubscriber testSubscriber = new TestSubscriber();
        Exception exception = new RuntimeException("error");

        Flowable.error(exception).subscribe(testSubscriber);
        //断言错误是否一致
        testSubscriber.assertError(exception);
        //断言错误信息是否一致
        testSubscriber.assertErrorMessage("error");
    }

7.interval

在测试有关时间的操作符时,可能我们的事件还没有执行完,因此无法得到预期的输出结果,断言就无效了。当然我们可以使用上一篇说到的将异步转为同步思路,如下:

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();
                }
            }
        };
    }
}

8.timer

@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();
    }

3.一个实战小例子

我们用获取验证码这个场景来检验今天的内容。点击发送验证码按钮,按钮不能再点击,倒计时120s,时间过了后,按钮就可以点击了。很简单的一个场景。

界面:

Android单元测试(六):RxJava测试_第1张图片

代码如下:

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。希望大家多多点赞支持!

你可能感兴趣的:(Android单元测试)