Android-cleanarchitecture: Testing the observers

Created on 23 Feb 2017  路  8Comments  路  Source: android10/Android-CleanArchitecture

Hi, I would like to test observers, based on the different results my UseCase may return. For example https://github.com/android10/Android-CleanArchitecture/blob/master/presentation/src/main/java/com/fernandocejas/android10/sample/presentation/presenter/UserListPresenter.java

In this case, onComplete(), onError() and onNext() are not tested. I'd like to be able to write a test named "testUserListObserverOnError" and make sure hideViewLoading(), showErrorMessage(), showViewRetry() are called.

I haven't managed to do that since the Observer class is a private inner class. I can not mock it. How would you go about testing this?

Most helpful comment

@quanturium @Bukoow

Let me share a solution which I usually use in such case.

For example, we have a screen with list of articles. To fetch articles we have an use case GetArticlesUseCase:

public class GetArticlesUseCase extends UseCase<List<Article>> {

    private final Repository repository;

    @Inject
    GetArticlesUseCase(Repository repository) {
        this.repository = repository;
    }

    @Override
    Observable<List<Article>> buildUseCaseObservable() {
        return repository.getArticles();
    }
}

That's our presenter:

public class ArticlesPresenter implements Presenter {

    private final GetArticlesUseCase getArticlesUseCase;

    @Inject
    ArticlesPresenter(GetArticlesUseCase getArticlesUseCase) {
        this.getArticlesUseCase = getArticlesUseCase;
    }    

    ...

    public void loadArticles() {
        getArticlesUseCase.execute(new ArticlesObserver());
    }

    ...

    private final class ArticlesObserver extends DefaultObserver<List<Article>> {

        @Override 
        public void onComplete() {
            view.hideLoading();
        }

        @Override 
        public void onError(Throwable error) {
            view.hideLoading();
            view.showError(error);
        }

        @Override 
        public void onNext(List<Article> articles) {
            view.showArticles(articles);
        }
    }    
}

Then how we can test that, a particular methods will be called from observer callbacks:

public class ArticlesPresenterTest {

    @Mock private ArticlesView articlesView;
    @Mock private GetArticlesUseCase getArticlesUseCase;

    // this is our savior 
    @Captor private ArgumentCaptor<DefaultObserver<List<Article>>> articlesObserverCaptor;

    @InjectMocks private ArticlesPresenter articlesPresenter;

    ...

    @Test
    public void loadArticles_showArticlesOnNoError() {
          articlesPresenter.loadArticles();

          verify(getArticlesUseCase).execute(articlesObserverCaptor);

          // articlesList is a stubbed List<Article> which you want to use for verifying         
          articlesObserverCaptor.getValue().onNext(articlesList);

          verify(articlesView).showArticles(eq(articlesList));
          verify(articlesView).hideLoading();
    }

    @Test
    public void loadArticles_showErrorMessageOnError() {
          articlesPresenter.loadArticles();

          verify(getArticlesUseCase).execute(articlesObserverCaptor);

          // exception is any Throwable which you want to use for verifying         
          articlesObserverCaptor.getValue().onError(exception);

          verify(articlesView).hideLoading();
          verify(articlesView).showError(eq(exception));
    }

    ...

}

After that observer callback methods will be marked as covered by tests.

You can find such approach in the popular android architecture blueprints repository. For example, TasksPresenter and TasksPresenterTest.

All 8 comments

@urizev Thanks for the links, however, they don't explain how to test what the Observer do. In the example I mentioned above, how would you test that the onError() calls some methods on the view?

You have some information ?

Maybe see something like a TestRule implementation overriding a TestRule class with theses methods :

public class RxSchedulersOverrideRule implements TestRule {

  @Override public Statement apply(final Statement base, Description d) {
    return new Statement() {
      @Override public void evaluate() throws Throwable {
        RxJavaPlugins.setIoSchedulerHandler(new Function<Scheduler, Scheduler>() {
          @Override public Scheduler apply(Scheduler scheduler) throws Exception {
            return Schedulers.trampoline();
          }
        });

        RxJavaPlugins.setComputationSchedulerHandler(new Function<Scheduler, Scheduler>() {
          @Override public Scheduler apply(Scheduler scheduler) throws Exception {
            return Schedulers.trampoline();
          }
        });

        RxJavaPlugins.setNewThreadSchedulerHandler(new Function<Scheduler, Scheduler>() {
          @Override public Scheduler apply(Scheduler scheduler) throws Exception {
            return Schedulers.trampoline();
          }
        });

        RxAndroidPlugins.setInitMainThreadSchedulerHandler(
            new Function<Callable<Scheduler>, Scheduler>() {
              @Override public Scheduler apply(Callable<Scheduler> schedulerCallable)
                  throws Exception {
                return Schedulers.trampoline();
              }
            });

        base.evaluate();
        RxJavaPlugins.reset();
        RxAndroidPlugins.reset();
      }
    };
  }
}

And use this class in presenter test :
@RunWith(MockitoJUnitRunner.class) public class PresenterTest extends TestWithRxSchedulersOverrideRule

But nothing works ...

(see :
https://medium.com/@peter.tackage/overriding-rxandroid-schedulers-in-rxjava-2-5561b3d14212
https://medium.com/@fabioCollini/testing-asynchronous-rxjava-code-using-mockito-8ad831a16877
)

need help ;)

First of all you need to understand that rule statement will be called after "before" method. I think you have to call trigger RxAndroidPlugins & RxJavaPlugins above in the static initializer of unit test class (static {...}) or static method annotated with BeforeClass. And don't reset trampoline scheduler, it is redundant.

In fact, i've already worked with this type of Android architecture (in a work project), but we used RxJava 1.X and when we called a presenter we didn't have a "use case", we called a presenter method like that :

@Override public void loadData() {
    compositeSubscription.add(dataService.getData()
        .map(new Func1<List<DataEntity>, DataViewModel>() {
          @Override public DataViewModelcall(List<DataEntity> dataEntities) {
            return dataEntitiesToDataViewModelMapper.transform(dataEntities);
          }
        })
        .subscribeOn(Schedulers.io())//
        .observeOn(AndroidSchedulers.mainThread())
        .subscribe(new Subscriber<DataViewModel>() {
          @Override public void onCompleted() {
            //Do Nothing
          }

          @Override public void onError(Throwable e) {
            view.displayDialogError();
            Timber.e(e, e.getMessage());
          }

          @Override public void onNext(DataViewModel dataViewModel) {
            view.populate(dataViewModel);
          }
        }));
  }
}

And in the unit test, we used the rule that I wrote before (with Rx Java 1 specifications) :

@RunWith(MockitoJUnitRunner.class) public class DataPresenterTest extends TestWithRxSchedulersOverrideRule {
  @Mock DataService dataService;
  @Mock DataEntitiesToDataViewModelMapper dataEntitiesToDataViewModelMapper;
  @Mock DataView view;
  private DataPresenter dataPresenter ;

  @Before public void setUp() throws Exception {
    dataPresenter = new DataPresenter (dataService, dataEntitiesToDataViewModelMapper);
    dataPresenter .attachView(view);
  }

  @After public void tearDown() throws Exception {
    dataPresenter .destroy();
  }

  /***********************************************************************************************/

  @Test public void should_loadAllInfrapole() throws Exception {
    List<DataEntity> dataEntities= new ArrayList<>();
    DataViewModel dataViewModel= new DataViewModel();
    when(dataService.getData()).thenReturn(Observable.just(dataEntities));
    when(dataEntitiesToDataViewModelMapper.transform(dataEntities)).thenReturn(dataViewModel);
    dataPresenter .loadData();

     /// Verification in OnNext method
    verify(view).populate(dataViewModel);
    verify(view, never()).displayDialogError();
  }
}

Here, when I try my solution or yours (called in @BeforeClass all RxAndroidPlugins.setInitMainThreadSchedulerHandler and cie), I always have the error :
Wanted but not invoked: view.myMethod();
Like if we don't have access to OnNext or OnError method...

@quanturium @Bukoow

Let me share a solution which I usually use in such case.

For example, we have a screen with list of articles. To fetch articles we have an use case GetArticlesUseCase:

public class GetArticlesUseCase extends UseCase<List<Article>> {

    private final Repository repository;

    @Inject
    GetArticlesUseCase(Repository repository) {
        this.repository = repository;
    }

    @Override
    Observable<List<Article>> buildUseCaseObservable() {
        return repository.getArticles();
    }
}

That's our presenter:

public class ArticlesPresenter implements Presenter {

    private final GetArticlesUseCase getArticlesUseCase;

    @Inject
    ArticlesPresenter(GetArticlesUseCase getArticlesUseCase) {
        this.getArticlesUseCase = getArticlesUseCase;
    }    

    ...

    public void loadArticles() {
        getArticlesUseCase.execute(new ArticlesObserver());
    }

    ...

    private final class ArticlesObserver extends DefaultObserver<List<Article>> {

        @Override 
        public void onComplete() {
            view.hideLoading();
        }

        @Override 
        public void onError(Throwable error) {
            view.hideLoading();
            view.showError(error);
        }

        @Override 
        public void onNext(List<Article> articles) {
            view.showArticles(articles);
        }
    }    
}

Then how we can test that, a particular methods will be called from observer callbacks:

public class ArticlesPresenterTest {

    @Mock private ArticlesView articlesView;
    @Mock private GetArticlesUseCase getArticlesUseCase;

    // this is our savior 
    @Captor private ArgumentCaptor<DefaultObserver<List<Article>>> articlesObserverCaptor;

    @InjectMocks private ArticlesPresenter articlesPresenter;

    ...

    @Test
    public void loadArticles_showArticlesOnNoError() {
          articlesPresenter.loadArticles();

          verify(getArticlesUseCase).execute(articlesObserverCaptor);

          // articlesList is a stubbed List<Article> which you want to use for verifying         
          articlesObserverCaptor.getValue().onNext(articlesList);

          verify(articlesView).showArticles(eq(articlesList));
          verify(articlesView).hideLoading();
    }

    @Test
    public void loadArticles_showErrorMessageOnError() {
          articlesPresenter.loadArticles();

          verify(getArticlesUseCase).execute(articlesObserverCaptor);

          // exception is any Throwable which you want to use for verifying         
          articlesObserverCaptor.getValue().onError(exception);

          verify(articlesView).hideLoading();
          verify(articlesView).showError(eq(exception));
    }

    ...

}

After that observer callback methods will be marked as covered by tests.

You can find such approach in the popular android architecture blueprints repository. For example, TasksPresenter and TasksPresenterTest.

Thanks @epetrenko. You're awesome!

@epetrenko I had the same setup as you outlined but was struggling test. I just discovered "Captor", read up about it but wasnt sure how to use, your solution was perfect in helping with this.

Thank you.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

risayew picture risayew  路  6Comments

rshah picture rshah  路  6Comments

ashraffouad picture ashraffouad  路  5Comments

ZherebtsovAlexandr picture ZherebtsovAlexandr  路  3Comments

donlucard picture donlucard  路  3Comments