Unit testing android application with retrofit and rxjava

前端 未结 6 399
旧巷少年郎
旧巷少年郎 2020-12-08 16:13

I have developed an android app that is using retrofit with rxJava, and now I\'m trying to set up the unit tests with Mockito but I don\'t know how to mock the api responses

6条回答
  •  星月不相逢
    2020-12-08 16:38

    How to test RxJava and Retrofit

    1. Get rid of the static call - use dependency injection

    The first problem in your code is that you use static methods. This is not a testable architecture, at least not easily, because it makes it harder to mock the implementation. To do things properly, instead of using Api that accesses ApiClient.getService(), inject this service to the presenter through the constructor:

    public class SplashPresenterImpl implements SplashPresenter {
    
    private SplashView splashView;
    private final ApiService service;
    
    public SplashPresenterImpl(SplashView splashView, ApiService service) {
        this.splashView = splashView;
        this.apiService = service;
    }
    

    2. Create the test class

    Implement your JUnit test class and initialize the presenter with mock dependencies in the @Before method:

    public class SplashPresenterImplTest {
    
    @Mock
    ApiService apiService;
    
    @Mock
    SplashView splashView;
    
    private SplashPresenter splashPresenter;
    
    @Before
    public void setUp() throws Exception {
        this.splashPresenter = new SplashPresenter(splashView, apiService);
    }
    

    3. Mock and test

    Then comes the actual mocking and testing, for example:

    @Test
    public void testEmptyListResponse() throws Exception {
        // given
        when(apiService.syncGenres()).thenReturn(Observable.just(Collections.emptyList());
        // when
        splashPresenter.syncGenres();
        // then
        verify(... // for example:, verify call to splashView.navigateToHome()
    }
    

    That way you can test your Observable + Subscription, if you want to test if the Observable behaves correctly, subscribe to it with an instance of TestSubscriber.


    Troubleshooting

    When testing with RxJava and RxAndroid schedulers, such as Schedulers.io() and AndroidSchedulers.mainThread() you might encounter several problems with running your observable/subscription tests.

    NullPointerException

    The first is NullPointerException thrown on the line that applies given scheduler, for example:

    .observeOn(AndroidSchedulers.mainThread()) // throws NPE
    

    The cause is that AndroidSchedulers.mainThread() is internally a LooperScheduler that uses android's Looper thread. This dependency is not available on JUnit test environment, and thus the call results in a NullPointerException.

    Race condition

    The second problem is that if applied scheduler uses a separate worker thread to execute observable, the race condition occurs between the thread that executes the @Test method and the said worker thread. Usually it results in test method returning before observable execution finishes.

    Solutions

    Both of the said problems can be easily solved by supplying test-compliant schedulers, and there are few options:

    1. Use RxJavaHooks and RxAndroidPlugins API to override any call to Schedulers.? and AndroidSchedulers.?, forcing the Observable to use, for example, Scheduler.immediate():

      @Before
      public void setUp() throws Exception {
              // Override RxJava schedulers
              RxJavaHooks.setOnIOScheduler(new Func1() {
                  @Override
                  public Scheduler call(Scheduler scheduler) {
                      return Schedulers.immediate();
                  }
              });
      
              RxJavaHooks.setOnComputationScheduler(new Func1() {
                  @Override
                  public Scheduler call(Scheduler scheduler) {
                      return Schedulers.immediate();
                  }
              });
      
              RxJavaHooks.setOnNewThreadScheduler(new Func1() {
                  @Override
                  public Scheduler call(Scheduler scheduler) {
                      return Schedulers.immediate();
                  }
              });
      
              // Override RxAndroid schedulers
              final RxAndroidPlugins rxAndroidPlugins = RxAndroidPlugins.getInstance();
              rxAndroidPlugins.registerSchedulersHook(new RxAndroidSchedulersHook() {
                  @Override
                  public Scheduler getMainThreadScheduler() {
                      return Schedulers.immediate();
              }
          });
      }
      
      @After
      public void tearDown() throws Exception {
          RxJavaHooks.reset();
          RxAndroidPlugins.getInstance().reset();
      }
      

      This code has to wrap the Observable test, so it can be done within @Before and @After as shown, it can be put into JUnit @Rule or placed anywhere in the code. Just don't forget to reset the hooks.

    2. Second option is to provide explicit Scheduler instances to classes (Presenters, DAOs) through dependency injection, and again just use Schedulers.immediate() (or other suitable for testing).

    3. As pointed out by @aleien, you can also use an injected RxTransformer instance that executes Scheduler application.

    I've used the first method with good results in production.

提交回复
热议问题