Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Testing the observers #223

Open
quanturium opened this issue Feb 23, 2017 · 8 comments
Open

Testing the observers #223

quanturium opened this issue Feb 23, 2017 · 8 comments

Comments

@quanturium
Copy link

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?

@urizev
Copy link

urizev commented Feb 23, 2017

@quanturium
Copy link
Author

@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?

@Bukoow
Copy link

Bukoow commented Jun 2, 2017

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

@ultraon
Copy link

ultraon commented Jun 2, 2017

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.

@Bukoow
Copy link

Bukoow commented Jun 3, 2017

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...

@epetrenko
Copy link

epetrenko commented Sep 13, 2017

@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.

@droidster
Copy link

Thanks @epetrenko. You're awesome!

@garfieldcoked
Copy link

@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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

7 participants