RxJava + Retrofit 2单元测试奇怪的错误

时间:2016-10-27 09:54:29

标签: android unit-testing mockito rx-java retrofit2

我正在使用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();

1 个答案:

答案 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调度程序时,它与运行测试的调度程序不同。

这将使测试在一个线程中运行,而订阅者/可观察者在另一个线程中运行。这可以解释为什么有时测试通过,有时他们没有。有竞争条件。

最简单的方法是确保在测试时observeOnsubscribeOn Schedulers.immediate(),并且在运行时你有正确的,{{1 }和Schedulers.io()

您可以通过覆盖调度程序,将它们作为构造函数传递来执行此操作,或者您甚至可以查看Dan {Le}解释如何使用AndroidSchedulers.mainThread()创建调度程序转换器的this。然后,您可以确保您的类在运行时使用适当的调度程序转换器,并在测试时使用一些变换器将所有内容放在直接线程上。