0

I'm trying to refactor one pretty old project, so I started implementing new architecture (MVVM) with Dagger2, RxJava, RxAndroid... Now everything is connected and working fine, now the problem is, I have no idea how to write a Unit test for my ViewModel..

I want to start with Login screen first, so I created a LoginViewModel, but first let me show you what I did..

I have a DataModule that provides 2 classes, RestApiRepository and ViewModelFactory. RestApiRepository looks like this:

public class RestApiRepository {

private RestClient restClient;

public RestApiRepository(RestClient restClient) {
    this.restClient = restClient;
}

public Observable<AuthResponseEntity> authenticate(String header, AuthRequestEntity requestEntity) {
    return restClient.postAuthObservable(header, requestEntity);
}
}

Rest client with api call for login:

public interface RestClient {

@POST(AUTH_URL)
Observable<AuthResponseEntity> postAuthObservable(@Header("Authorization") String authKey, @Body AuthRequestEntity requestEntity);
}

Second class from DataModule is ViewModelFactory:

@Singleton
public class ViewModelFactory extends ViewModelProvider.NewInstanceFactory implements ViewModelProvider.Factory {

private RestApiRepository repository;

@Inject
public ViewModelFactory(RestApiRepository repository) {
    this.repository = repository;
}


@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
    if (modelClass.isAssignableFrom(LoginViewModel.class)) {
        return (T) new LoginViewModel(repository);
    }
    throw new IllegalArgumentException("Unknown class name");
}
}

And finally, LoginViewModel:

public class LoginViewModel extends ViewModel {

private final CompositeDisposable disposable = new CompositeDisposable();
private final MutableLiveData<AuthResponseEntity> responseLiveData = new MutableLiveData<>();
private RestApiRepository restApiRepository;
private SchedulerProvider provider;

public LoginViewModel(RestApiRepository restApiRepository, SchedulerProvider provider) {
    this.restApiRepository = restApiRepository;
    this.provider = provider;
}

public MutableLiveData<AuthResponseEntity> getResponseLiveData() {
    return responseLiveData;
}

@Override
protected void onCleared() {
    disposable.clear();
}

public void auth(String token, AuthRequestEntity requestEntity) {
    if (token != null && requestEntity != null) {
        disposable.add(restApiRepository.authenticate(token, requestEntity)
                .subscribeOn(provider.io())
                .observeOn(provider.ui())
                .subscribeWith(new DisposableObserver<AuthResponseEntity>() {
                                   @Override
                                   public void onNext(AuthResponseEntity authResponseEntity) {
                                       responseLiveData.setValue(authResponseEntity);
                                   }

                                   @Override
                                   public void onError(Throwable e) {
                                       AuthResponseEntity authResponseEntity = new AuthResponseEntity();
                                       authResponseEntity.setErrorMessage(e.getMessage());
                                       responseLiveData.setValue(authResponseEntity);
                                   }

                                   @Override
                                   public void onComplete() {

                                   }
                               }
                ));
    }
}
}

So, I'm sure everything is connected well, I can successfuly login...

For the RxAndroid test issues, I found somewhere that I have to use this Scheduler provider like this:

public class AppSchedulerProvider implements SchedulerProvider {

public AppSchedulerProvider() {

}

@Override
public Scheduler computation() {
    return Schedulers.trampoline();
}

@Override
public Scheduler io() {
    return Schedulers.trampoline();
}

@Override
public Scheduler ui() {
    return Schedulers.trampoline();
}
}

Below is my LoginViewModelTest class, but I don't know how to handle RxJava/RxAndroid inside the tests..

@RunWith(MockitoJUnitRunner.class)
public class LoginViewModelTest {

@Mock
private RestApiRepository restApiRepository;

@Mock
private MutableLiveData<AuthResponseEntity> mutableLiveData;

private LoginViewModel loginViewModel;


@Before
public void setUp() {
    MockitoAnnotations.initMocks(this);

    AppSchedulerProvider schedulerProvider = new AppSchedulerProvider();

    loginViewModel = Mockito.spy(new LoginViewModel(restApiRepository, schedulerProvider));
}


@Test
public void authenticate_error() {
    String token = "token";
    AuthRequestEntity requestEntity = Mockito.mock(AuthRequestEntity.class);
    Mockito.doReturn(Observable.error(new Throwable())).when(restApiRepository).authenticate(token, requestEntity);
    loginViewModel.auth(token, requestEntity);
    AuthResponseEntity responseEntity = Mockito.mock(AuthResponseEntity.class);
    responseEntity.setErrorMessage("Error");
    Mockito.verify(mutableLiveData).setValue(responseEntity);
}
}

So, I wanted to write a test for failed case when onError is called, but when I run it, I get this error:

exclude patterns:io.reactivex.exceptions.UndeliverableException: The exception could not be delivered to the consumer because it has already canceled/disposed the flow or the exception has nowhere to go to begin with. Further reading: https://github.com/ReactiveX/RxJava/wiki/What's-different-in-2.0#error-handling | java.lang.RuntimeException: Method getMainLooper in android.os.Looper not mocked. See http://g.co/androidstudio/not-mocked for details.
joe
  • 1,341
  • 4
  • 21
  • 32
  • I would suggest you to edit your question and leave only the code of the class that you want to test (LoginViewModel), the efforts you made in trying to test it (some code of the unit tests that you started writing), and then explain where you got stuck – Gustavo Pagani Jun 12 '19 at 13:46
  • Will do so, tnx @Gustavo – joe Jun 12 '19 at 13:52

1 Answers1

0

You can mock the behaviour of restApiRepository:

Mockito.when(restApiRepository.authenticate(token, requestEntity)).thenReturn(Observable.error(error));

and verify that responseLiveData.setValue is being called with the appropriate parameters

Gustavo Pagani
  • 6,583
  • 5
  • 40
  • 71
  • I edited my question (changed my test class), please take a look at the exception I'm getting – joe Jun 12 '19 at 18:14