RxAndroid,Retrofit 2单元测试Schedulers.io

时间:2017-10-18 07:05:10

标签: android unit-testing retrofit2 rx-android rx-java2

我刚学会了RxAndroid,但遗憾的是我研究过的这本书没有涵盖任何单元测试。我在google上搜索了很多,但未能找到任何以精确方式覆盖RxAndroid单元测试的简单教程。

我基本上使用RxAndroid和Retrofit 2编写了一个小型REST API。这是ApiManager类:

public class MyAPIManager {
    private final MyService myService;

    public MyAPIManager() {
        HttpLoggingInterceptor logging = new HttpLoggingInterceptor();
        // set your desired log level
        logging.setLevel(HttpLoggingInterceptor.Level.BODY);

        OkHttpClient.Builder b = new OkHttpClient.Builder();
        b.readTimeout(35000, TimeUnit.MILLISECONDS);
        b.connectTimeout(35000, TimeUnit.MILLISECONDS);
        b.addInterceptor(logging);
        OkHttpClient client = b.build();

        Retrofit retrofit = new Retrofit.Builder()
                .addCallAdapterFactory(RxJava2CallAdapterFactory.create())
                .addConverterFactory(GsonConverterFactory.create())
                .baseUrl("http://192.168.1.7:8000")
                .client(client)
                .build();

        myService = retrofit.create(MyService.class);
    }

    public Observable<Token> getToken(String username, String password) {
        return myService.getToken(username, password)
                .subscribeOn(Schedulers.io());
                .observeOn(AndroidSchedulers.mainThread());
    }
}

我正在尝试为getToken创建单元测试。这是我的样本测试:

public class MyAPIManagerTest {
    private MyAPIManager myAPIManager;
    @Test
    public void getToken() throws Exception {
        myAPIManager = new MyAPIManager();

        Observable<Token> o = myAPIManager.getToken("hello", "mytoken");
        o.test().assertSubscribed();
        o.test().assertValueCount(1);
    }

}

由于subscribeOn(Schedulers.io),上述测试不会在主线程上运行,因为它返回0值。如果我从subscribeOn(Schedulers.io)移除MyAPIManager,那么它运行良好并返回1值。有没有办法用Schedulers.io进行测试?

1 个答案:

答案 0 :(得分:9)

很棒的问题,当然还有一个话题在社区中缺乏很多报道。我想分享一些我个人使用的解决方案并且非常精彩。这些被认为是RxJava 2,但它们可以在不同的名称下使用RxJava 1。如果需要,你肯定会找到它。

  1. RxPlugins和RxAndroidPlugins (这是我最喜欢的)
  2. 因此,Rx实际上提供了一种机制来更改SchedulersAndroidSchedulers中静态方法提供的调度程序。例如:

    RxJavaPlugins.setComputationSchedulerHandler
    RxJavaPlugins.setIoSchedulerHandler
    RxJavaPlugins.setNewThreadSchedulerHandler
    RxJavaPlugins.setSingleSchedulerHandler
    RxAndroidPlugins.setInitMainThreadSchedulerHandler
    

    这些操作非常简单。他们确保在您调用Schedulers.io()时,返回的调度程序是您在setIoSchedulerHandler中设置的处理程序中提供的调度程序。您想使用哪个调度程序?那么你想要Schedulers.trampoline()。这意味着代码将在以前的相同线程上运行。如果所有调度程序都在trampoline调度程序中,那么所有调度程序都将在JUnit线程上运行。测试运行后,您可以通过调用:

    来清理整个测试
    RxJavaPlugins.reset()
    RxAndroidPlugins.reset()
    

    我认为最好的方法是使用JUnit规则。这是一个可能的(抱歉kotlin语法):

    class TrampolineSchedulerRule : TestRule {
      private val scheduler by lazy { Schedulers.trampoline() }
    
      override fun apply(base: Statement?, description: Description?): Statement =
            object : Statement() {
                override fun evaluate() {
                    try {
                        RxJavaPlugins.setComputationSchedulerHandler { scheduler }
                        RxJavaPlugins.setIoSchedulerHandler { scheduler }
                        RxJavaPlugins.setNewThreadSchedulerHandler { scheduler }
                        RxJavaPlugins.setSingleSchedulerHandler { scheduler }
                        RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler }
                        base?.evaluate()
                    } finally {
                        RxJavaPlugins.reset()
                        RxAndroidPlugins.reset()
                    }
                }
            }
    }
    

    在单元测试的顶部,您只需要声明一个使用@Rule注释的公共属性,并使用此类进行实例化:

    @Rule
    public TrampolineSchedulerRule rule = new TrampolineSchedulerRule()
    

    在kotlin

    @get:Rule
    val rule = TrampolineSchedulerRule()
    
    1. 注入调度程序(a.k.a.依赖注入)
    2. 另一种可能性是在您的类中注入调度程序,因此在测试时您可以再次注入Schedulers.trampoline(),并且在您的应用程序中,您可以注入正常的调度程序。这可能会有一段时间,但是当你需要为一个简单的类注入大量的调度程序时,它很快就会变得很麻烦。这是实现此目的的一种方式

      public class MyAPIManager {
        private final MyService myService;
        private final Scheduler io;
        private final Scheduler mainThread;
      
        public MyAPIManager(Scheduler io, Scheduler mainThread) {
          // initialise everything
          this.io = io;
          this.mainThread = mainThread;
        }
      
        public Observable<Token> getToken(String username, String password) {
          return myService.getToken(username, password)
                  .subscribeOn(io);
                  .observeOn(mainThread);
        }
      }
      

      正如您所看到的,我们现在可以告诉班级实际的调度程序。在你的测试中你会做类似的事情:

      public class MyAPIManagerTest {
        private MyAPIManager myAPIManager;
        @Test
        public void getToken() throws Exception {
          myAPIManager = new MyAPIManager(
                Schedulers.trampoline(),
                Schedulers.trampoline());
      
          Observable<Token> o = myAPIManager.getToken("hello", "mytoken");
          o.test().assertSubscribed();
          o.test().assertValueCount(1);
        }
      }
      

      关键点是:

      • 你想在Schedulers.trampoline()调度程序上确保一切都在JUnit线程上运行

      • 您需要在测试时修改调度程序。

      这就是全部。希望它有所帮助。

      =============================================== ==========

      以下是我在遵循上述Kotlin示例后使用的Java版本:

      public class TrampolineSchedulerRule implements TestRule {
          @Override
          public Statement apply(Statement base, Description description) {
              return new MyStatement(base);
          }
      
          public class MyStatement extends Statement {
              private final Statement base;
      
              @Override
              public void evaluate() throws Throwable {
                  try {
                      RxJavaPlugins.setComputationSchedulerHandler(scheduler -> Schedulers.trampoline());
                      RxJavaPlugins.setIoSchedulerHandler(scheduler -> Schedulers.trampoline());
                      RxJavaPlugins.setNewThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
                      RxJavaPlugins.setSingleSchedulerHandler(scheduler -> Schedulers.trampoline());
                      RxAndroidPlugins.setInitMainThreadSchedulerHandler(scheduler -> Schedulers.trampoline());
                      base.evaluate();
                  } finally {
                      RxJavaPlugins.reset();
                      RxAndroidPlugins.reset();
                  }
              }
      
              public MyStatement(Statement base) {
                  this.base = base;
              }
          }
      }