【发布时间】:2019-06-12 12:30:10
【问题描述】:
我正在尝试重构一个相当老的项目,所以我开始使用 Dagger2、RxJava、RxAndroid 实现新架构 (MVVM)... 现在一切都已连接并且工作正常,现在问题是,我不知道如何为我的 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 调用登录的 Rest 客户端:
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.
【问题讨论】:
-
我建议你编辑你的问题,只留下你想要测试的类的代码(LoginViewModel),你在尝试测试它时所做的努力(单元测试的一些代码你开始写作),然后解释你卡在哪里
-
会这样做的,tnx @Gustavo
标签: unit-testing mockito rx-java2 rx-android android-mvvm