hboy
hboy

Reputation: 311

Verify mock interactions within anonymous inner class

I am trying to test my ViewModel in my application, here is the constructor:

@Inject
public SearchUserViewModel(@Named("searchUser") UseCase searchUserUseCase) {
  this.searchUserUseCase = searchUserUseCase;
}

In my test I create a SearchUserUseCase with mocks like this:

Observable error = Observable.error(new Throwable("Error"));
when(gitHubService.searchUser(MockFactory.TEST_USERNAME_ERROR)).thenReturn(error);

when(ObserverThread.getScheduler()).thenReturn(Schedulers.immediate());
when(SubscriberThread.getScheduler()).thenReturn(Schedulers.immediate());

searchUserUseCase = new SearchUserUseCase(gitHubService, SubscriberThread, ObserverThread);

In my ViewModel class I have this snippet which I want to test:

public void onClickSearch(View view) {
  loadUsers();
}

private void loadUsers() {
  if (username == null) {
    fragmentListener.showMessage("Enter a username");
  } else {
    showProgressIndicator(true);
    searchUserUseCase.execute(new SearchUserSubscriber(), username);
  }
}

private final class SearchUserSubscriber extends DefaultSubscriber<SearchResponse> {

  @Override
  public void onCompleted() {
    showProgressIndicator(false);
  }

  @Override
  public void onError(Throwable e) {
    showProgressIndicator(false);
    fragmentListener.showMessage("Error loading users");
  }

  @Override
  public void onNext(SearchResponse searchResponse) {
    List<User> users = searchResponse.getUsers();
    if (users.isEmpty()) {
      fragmentListener.showMessage("No users found");
    } else {
      fragmentListener.addUsers(users);
    }
  }

}

Finally in my test I have this:

@Test
public void shouldDisplayErrorMessageIfErrorWhenLoadingUsers() {
  SearchUserViewModel searchUserViewModel = new SearchUserViewModel(searchUserUseCase);
  searchUserViewModel.setFragmentListener(mockFragmentListener);
  searchUserViewModel.setUsername(MockFactory.TEST_USERNAME_ERROR);
  searchUserViewModel.onClickSearch(view);
  verify(mockFragmentListener).showMessage("Error loading users");
}

I get this error from Mockito:

Wanted but not invoked:
fragmentListener.showMessage(
  "Error loading users"
);

I am not sure if this is a good test, but I somehow want to test the SearchUserSubscriber one way or another. Thanks

Edit: I have found similar questions to this problem here: Can't verify mock method call from RxJava Subscriber (which still isn't answered) and here: Verify interactions in rxjava subscribers. The latter question is similar but does not execute the subscriber in a separate class (which happens in SearchUserUseCase here).

I also tried RobolectricGradleTestRunner instead of MockitoJunitRunner and changed to Schedulers.io() and AndroidSchedulers.mainThread(), but I still get the same error.

Tried mocking SearchUserUseCase instead of GitHubService (which feels cleaner), but I'm not sure on how to test the subscriber that way since that is passed as an argument to the void method execute() in UseCase.

public void execute(Subscriber useCaseSubscriber, String query) {
  subscription = buildUseCase(query)
    .observeOn(postExecutionThread.getScheduler())
    .subscribeOn(threadExecutor.getScheduler())
    .subscribe(useCaseSubscriber);
}

And buildUseCase()

@Override
public Observable buildUseCase(String username) throws NullPointerException {
  if (username == null) {
    throw new NullPointerException("Query must not be null");
  }
  return getGitHubService().searchUser(username);
}

Upvotes: 1

Views: 315

Answers (2)

Makzimalist
Makzimalist

Reputation: 53

For me it worked out to add a Observable.Transformer<T, T> as followed:

void gatherData() {
    service.doSomeMagic()
      .compose(getSchedulerTransformer())
      .subscribe(view::displayValue);
}

private <T> Observable.Transformer<T, T> getSchedulerTransformer() {
    if (mTransformer == null) {
        mTransformer = (Observable.Transformer<T, T>) observable -> observable.subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread());
    }

    return mTransformer;
}

void setSchedulerTransformer(Observable.Transformer<Observable<?>, Observable<?>> transformer) {
    mTransformer = transformer;
}

And to set the Transformer. I just passed this:

setSchedulerTransformer(observable -> {
    if (observable instanceof Observable) {
        Observable observable1 = (Observable) observable;
        return observable1.subscribeOn(Schedulers.immediate())
                .observeOn(Schedulers.immediate());
    }
    return null;
});

So just add a @Before method in your test and call presenter.setSchedulerTransformer and it should be able to test this. If you want more detail check this answer.

Upvotes: 1

BretC
BretC

Reputation: 4209

If you are using Mockito, you can probably get hold of a SearchUserSubscriber using an ArgumentCaptor, for example...

@Captor
private ArgumentCaptor<SearchUserSubscriber> subscriberCaptor;

private SearchUserSubscriber getSearchUserSubscriber() {

    // TODO: ...set up the view model...
    ...
    // Execute the code under test (making sure the line 'searchUserUseCase.execute(new SearchUserSubscriber(), username);' gets hit...)
    viewModel.onClickSearch(view);

    verify(searchUserUseCase).execute(subscriberCaptor.capture(), any(String.class));

    return subscriberCaptor.getValue();
}

Now you can have test cases such as...

@Test
public void shouldDoSomethingWithTheSubscriber() {
    SearchUserSubscriber subscriber = getSearchUserSubscriber();

    ...
}

Upvotes: 0

Related Questions