Как написать модульный тест для ViewModel, который содержит RxJava/RxAndroid

Я пытаюсь рефакторить один довольно старый проект, поэтому я начал внедрять новую архитектуру (MVVM) с Dagger2, RxJava, RxAndroid... Теперь все подключено и работает нормально, теперь проблема в том, что я понятия не имею, как написать Модульный тест для моей ViewModel..

Сначала я хочу начать с экрана входа в систему, поэтому я создал LoginViewModel, но сначала позвольте мне показать вам, что я сделал.

У меня есть DataModule, который предоставляет 2 класса: RestApiRepository и ViewModelFactory. RestApiRepository выглядит так:

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

Клиент отдыха с вызовом API для входа в систему:

public interface RestClient {

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

Второй класс из DataModule — 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");
}
}

И, наконец, 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() {

                                   }
                               }
                ));
    }
}
}

Итак, я уверен, что все подключено правильно, я могу успешно войти в систему...

Что касается проблем с тестами RxAndroid, я где-то нашел, что мне нужно использовать этого поставщика планировщика следующим образом:

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

Ниже приведен мой класс LoginViewModelTest, но я не знаю, как обрабатывать RxJava/RxAndroid внутри тестов.

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

Итак, я хотел написать тест для неудачного случая, когда вызывается onError, но когда я его запускаю, я получаю эту ошибку:

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.

person joe    schedule 12.06.2019    source источник
comment
Я бы предложил вам отредактировать свой вопрос и оставить только код класса, который вы хотите протестировать (LoginViewModel), усилия, которые вы приложили, пытаясь его протестировать (некоторый код модульных тестов, которые вы начали писать), а затем объяснить где ты застрял   -  person Gustavo    schedule 12.06.2019
comment
Так и сделаю, спасибо @Gustavo   -  person joe    schedule 12.06.2019


Ответы (1)


Вы можете издеваться над поведением restApiRepository:

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

и убедитесь, что responseLiveData.setValue вызывается с соответствующими параметрами

person Gustavo    schedule 12.06.2019
comment
Я отредактировал свой вопрос (изменил свой тестовый класс), пожалуйста, взгляните на исключение, которое я получаю - person joe; 12.06.2019