我正在使用MVP模式创建Android应用程序。 为此,我使用的是Retrofit 2和RxJava。应用程序正常
但是在单元测试中,我得到了奇怪的错误。有时测试代码通过,有时会失败。
显示此消息的错误
Wanted but not invoked:
albumView.showProgress();
-> at kz.afckairat.kairat.media.AlbumPresenterTest.checkGetPhotoAlbums(AlbumPresenterTest.java:66)
Actually, there were zero interactions with this mock.
测试课
public class AlbumPresenterTest {
enter code here
private MediaService mediaService;
private AlbumView albumView;
private AlbumPresenterImpl photoAlbumPresenter;
@Before
public void setUp() throws Exception {
albumView = mock(AlbumView.class);
mediaService = mock(MediaService.class);
photoAlbumPresenter = new AlbumPresenterImpl(albumView, mediaService, MediaType.PHOTO);
RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
@Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
}
@After
public void tearDown() {
RxAndroidPlugins.getInstance().reset();
}
@Test
public void checkGetPhotoAlbums() {
List<Album> albums = getAlbumList();
when(mediaService.getPhotoAlbums()).thenReturn(Observable.just(albums));
photoAlbumPresenter.getAlbums();
verify(albumView).showProgress();
verify(albumView).showAlbums(albums);
verify(albumView).hideProgress();
}
@Test
public void checkGetPhotoAlbumError() {
String msg = "Error";
when(mediaService.getPhotoAlbums()).thenReturn(Observable.error(new IOException(msg)));
photoAlbumPresenter.getAlbums();
verify(albumView).showProgress();
verify(albumView).showError(msg);
verify(albumView).hideProgress();
}
private List<Album> getAlbumList() {
List<Album> albums = new ArrayList<>();
Album album = new Album(1, "Test1", "test1.jpg", "01.01.2016", 2);
albums.add(album);
album = new Album(2, "Test2", "test2.jpg", "01.01.2016", 2);
albums.add(album);
return albums;
}
}
经过测试的Presenter类
public class AlbumPresenterImpl implements AlbumPresenter {
private AlbumView view;
private MediaType type;
private List<Album> albums;
private MediaService mediaService;
public AlbumPresenterImpl(AlbumView view, MediaService mediaService, MediaType type) {
this.view = view;
this.mediaService = mediaService;
this.type = type;
}
@Override
public void getAlbums() {
Observable<List<Album>> observable;
if (type.equals(MediaType.VIDEO)) {
observable = mediaService.getVideoAlbums();
} else {
observable = mediaService.getPhotoAlbums();
}
observable.doOnSubscribe(view::showProgress)
.doAfterTerminate(view::hideProgress)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(items -> {
albums = items;
view.showAlbums(albums);
}, throwable -> {
view.showError(throwable.getLocalizedMessage());
});
}
@Override
public void onResume() {
if (albums == null) {
getAlbums();
}
}
@Override
public void onDestroy() {
}
}
为什么有时候考试不通过?
非常感谢!
=================================
更新
由于@Fred写的问题是在调度程序中
public class RxSchedulersOverrideRule implements TestRule {
private final RxJavaSchedulersHook mRxJavaSchedulersHook = new RxJavaSchedulersHook() {
@Override
public Scheduler getIOScheduler() {
return Schedulers.immediate();
}
@Override
public Scheduler getNewThreadScheduler() {
return Schedulers.immediate();
}
};
private final RxAndroidSchedulersHook mRxAndroidSchedulersHook = new RxAndroidSchedulersHook() {
@Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
};
// Hack to get around RxJavaPlugins.reset() not being public
// See https://github.com/ReactiveX/RxJava/issues/2297
// Hopefully the method will be public in new releases of RxAndroid and we can remove the hack.
private void callResetViaReflectionIn(RxJavaPlugins rxJavaPlugins)
throws InvocationTargetException, IllegalAccessException, NoSuchMethodException {
Method method = rxJavaPlugins.getClass().getDeclaredMethod("reset");
method.setAccessible(true);
method.invoke(rxJavaPlugins);
}
@Override
public Statement apply(final Statement base, Description description) {
return new Statement() {
@Override
public void evaluate() throws Throwable {
RxAndroidPlugins.getInstance().reset();
RxAndroidPlugins.getInstance().registerSchedulersHook(mRxAndroidSchedulersHook);
callResetViaReflectionIn(RxJavaPlugins.getInstance());
RxJavaPlugins.getInstance().registerSchedulersHook(mRxJavaSchedulersHook);
base.evaluate();
RxAndroidPlugins.getInstance().reset();
callResetViaReflectionIn(RxJavaPlugins.getInstance());
}
};
}
}
代码来自Github a link!
在Test class中
@Rule
public final RxSchedulersOverrideRule mOverrideSchedulersRule = new RxSchedulersOverrideRule();
答案 0 :(得分:1)
您似乎用:
覆盖主线程调度程序RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
@Override
public Scheduler getMainThreadScheduler() {
return Schedulers.immediate();
}
});
但是从代码中,observable仍然在Schedulers.io()
调度程序上运行:
observable.doOnSubscribe(view::showProgress)
.doAfterTerminate(view::hideProgress)
.subscribeOn(Schedulers.io())
// ...
正如您所知,立即调度程序在当前线程中执行代码,我猜您自己跳转到io
调度程序时,它与运行测试的调度程序不同。
这将使测试在一个线程中运行,而订阅者/可观察者在另一个线程中运行。这可以解释为什么有时测试通过,有时他们没有。有竞争条件。
最简单的方法是确保在测试时observeOn
和subscribeOn
Schedulers.immediate()
,并且在运行时你有正确的,{{1 }和Schedulers.io()
。
您可以通过覆盖调度程序,将它们作为构造函数传递来执行此操作,或者您甚至可以查看Dan {Le}解释如何使用AndroidSchedulers.mainThread()
创建调度程序转换器的this。然后,您可以确保您的类在运行时使用适当的调度程序转换器,并在测试时使用一些变换器将所有内容放在直接线程上。