Open quanturium opened 7 years ago
I recommend you these links:
I hope they're helpful
@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.
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?