使用改造和rxjava单元测试android应用程序 [英] Unit testing android application with retrofit and rxjava

查看:68
本文介绍了使用改造和rxjava单元测试android应用程序的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我已经开发了一个使用rxJava进行改造的android应用,现在我正在尝试使用Mockito设置单元测试,但是我不知道如何模拟api响应以创建不支持的测试进行真正的通话,但有虚假的回复.

I have developed an android app that is using retrofit with rxJava, and now I'm trying to set up the unit tests with Mockito but I don't know how to mock the api responses in order to create tests that do not do the real calls but have fake responses.

例如,我要测试方法syncGenres是否对我的SplashPresenter正常工作.我的课程如下:

For instance, I want to test that the method syncGenres is working fine for my SplashPresenter. My classes are as follow:

public class SplashPresenterImpl implements SplashPresenter {

private SplashView splashView;

public SplashPresenterImpl(SplashView splashView) {
    this.splashView = splashView;
}

@Override
public void syncGenres() {
    Api.syncGenres(new Subscriber<List<Genre>>() {
        @Override
        public void onError(Throwable e) {
            if(splashView != null) {
                splashView.onError();
            }
        }

        @Override
        public void onNext(List<Genre> genres) {
            SharedPreferencesUtils.setGenres(genres);
            if(splashView != null) {
                splashView.navigateToHome();
            }
        }
    });
}
}

Api类类似于:

public class Api {
    ...
    public static Subscription syncGenres(Subscriber<List<Genre>> apiSubscriber) {
        final Observable<List<Genre>> call = ApiClient.getService().syncGenres();
        return call
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(apiSubscriber);
    }

}

现在我正在尝试测试SplashPresenterImpl类,但是我不知道该怎么做,我应该做类似的事情:

Now I'm trying to test the SplashPresenterImpl class but I don't know how to do that, I should do something like:

public class SplashPresenterImplTest {

@Mock
Api api;
@Mock
private SplashView splashView;

@Captor
private ArgumentCaptor<Callback<List<Genre>>> cb;

private SplashPresenterImpl splashPresenter;

@Before
public void setupSplashPresenterTest() {
    // Mockito has a very convenient way to inject mocks by using the @Mock annotation. To
    // inject the mocks in the test the initMocks method needs to be called.
    MockitoAnnotations.initMocks(this);

    // Get a reference to the class under test
    splashPresenter = new SplashPresenterImpl(splashView);
}

@Test
public void syncGenres_success() {

    Mockito.when(api.syncGenres(Mockito.any(ApiSubscriber.class))).thenReturn(); // I don't know how to do that

    splashPresenter.syncGenres();
    Mockito.verify(api).syncGenres(Mockito.any(ApiSubscriber.class)); // I don't know how to do that



}
}

您对我应该如何模拟和验证api响应有任何想法吗? 预先感谢!

Do you have any idea about how should I mock and verify the api responses? Thanks in advance!

按照@invariant的建议,现在我将一个客户端对象传递给我的演示者,并且该api返回一个Observable而不是Subscription.但是,在进行api调用时,我的订阅服务器上出现了NullPointerException异常.测试类如下:

Following @invariant suggestion, now I'm passing a client object to my presenter, and that api returns an Observable instead of a Subscription. However, I'm getting a NullPointerException on my Subscriber when doing the api call. The test class looks like:

public class SplashPresenterImplTest {
@Mock
Api api;
@Mock
private SplashView splashView;

private SplashPresenterImpl splashPresenter;

@Before
public void setupSplashPresenterTest() {
    // Mockito has a very convenient way to inject mocks by using the @Mock annotation. To
    // inject the mocks in the test the initMocks method needs to be called.
    MockitoAnnotations.initMocks(this);

    // Get a reference to the class under test
    splashPresenter = new SplashPresenterImpl(splashView, api);
}

@Test
public void syncGenres_success() {
    Mockito.when(api.syncGenres()).thenReturn(Observable.just(Collections.<Genre>emptyList()));


    splashPresenter.syncGenres();


    Mockito.verify(splashView).navigateToHome();
}
}

我为什么会收到NullPointerException?

Why am I getting that NullPointerException?

非常感谢!

推荐答案

如何测试RxJava和改造

1.摆脱静态调用-使用依赖注入

代码中的第一个问题是您使用静态方法.这不是一个可测试的体系结构,至少不容易,因为它使模拟实现变得更加困难. 为了正确执行操作,而不是使用访问ApiClient.getService()Api,而是通过构造函数向演示者注入此服务:

How to test RxJava and Retrofit

1. Get rid of the static call - use dependency injection

The first problem in your code is that you use static methods. This is not a testable architecture, at least not easily, because it makes it harder to mock the implementation. To do things properly, instead of using Api that accesses ApiClient.getService(), inject this service to the presenter through the constructor:

public class SplashPresenterImpl implements SplashPresenter {

private SplashView splashView;
private final ApiService service;

public SplashPresenterImpl(SplashView splashView, ApiService service) {
    this.splashView = splashView;
    this.apiService = service;
}

2.创建测试课程

@Before方法中实现JUnit测试类并使用模拟依赖项初始化演示者:

2. Create the test class

Implement your JUnit test class and initialize the presenter with mock dependencies in the @Before method:

public class SplashPresenterImplTest {

@Mock
ApiService apiService;

@Mock
SplashView splashView;

private SplashPresenter splashPresenter;

@Before
public void setUp() throws Exception {
    this.splashPresenter = new SplashPresenter(splashView, apiService);
}

3.模拟并测试

然后是实际的模拟和测试,例如:

3. Mock and test

Then comes the actual mocking and testing, for example:

@Test
public void testEmptyListResponse() throws Exception {
    // given
    when(apiService.syncGenres()).thenReturn(Observable.just(Collections.emptyList());
    // when
    splashPresenter.syncGenres();
    // then
    verify(... // for example:, verify call to splashView.navigateToHome()
}

这样,您可以测试Observable +订阅,如果要测试Observable的行为是否正确,请使用TestSubscriber实例进行订阅.

That way you can test your Observable + Subscription, if you want to test if the Observable behaves correctly, subscribe to it with an instance of TestSubscriber.

在使用RxJava和RxAndroid调度程序(例如Schedulers.io()AndroidSchedulers.mainThread())进行测试时,在运行可观察/订阅测试时可能会遇到一些问题.

When testing with RxJava and RxAndroid schedulers, such as Schedulers.io() and AndroidSchedulers.mainThread() you might encounter several problems with running your observable/subscription tests.

第一个是在应用给定调度程序的行上抛出的NullPointerException,例如:

The first is NullPointerException thrown on the line that applies given scheduler, for example:

.observeOn(AndroidSchedulers.mainThread()) // throws NPE

原因是AndroidSchedulers.mainThread()在内部是使用Android的Looper线程的LooperScheduler.此依赖关系在JUnit测试环境中不可用,因此调用会导致NullPointerException.

The cause is that AndroidSchedulers.mainThread() is internally a LooperScheduler that uses android's Looper thread. This dependency is not available on JUnit test environment, and thus the call results in a NullPointerException.

第二个问题是,如果应用的调度程序使用单独的工作线程执行可观察的任务,则竞争条件发生在执行@Test方法的线程与所述工作线程之间.通常,它会导致测试方法在可观察到的执行完成之前返回.

The second problem is that if applied scheduler uses a separate worker thread to execute observable, the race condition occurs between the thread that executes the @Test method and the said worker thread. Usually it results in test method returning before observable execution finishes.

通过提供符合测试要求的调度程序,可以轻松解决上述两个问题,并且选择很少:

Both of the said problems can be easily solved by supplying test-compliant schedulers, and there are few options:

  1. 使用RxJavaHooksRxAndroidPlugins API覆盖对Schedulers.?AndroidSchedulers.?的任何调用,从而强制使用Observable,例如Scheduler.immediate():

  1. Use RxJavaHooks and RxAndroidPlugins API to override any call to Schedulers.? and AndroidSchedulers.?, forcing the Observable to use, for example, Scheduler.immediate():

@Before
public void setUp() throws Exception {
        // Override RxJava schedulers
        RxJavaHooks.setOnIOScheduler(new Func1<Scheduler, Scheduler>() {
            @Override
            public Scheduler call(Scheduler scheduler) {
                return Schedulers.immediate();
            }
        });

        RxJavaHooks.setOnComputationScheduler(new Func1<Scheduler, Scheduler>() {
            @Override
            public Scheduler call(Scheduler scheduler) {
                return Schedulers.immediate();
            }
        });

        RxJavaHooks.setOnNewThreadScheduler(new Func1<Scheduler, Scheduler>() {
            @Override
            public Scheduler call(Scheduler scheduler) {
                return Schedulers.immediate();
            }
        });

        // Override RxAndroid schedulers
        final RxAndroidPlugins rxAndroidPlugins = RxAndroidPlugins.getInstance();
        rxAndroidPlugins.registerSchedulersHook(new RxAndroidSchedulersHook() {
            @Override
            public Scheduler getMainThreadScheduler() {
                return Schedulers.immediate();
        }
    });
}

@After
public void tearDown() throws Exception {
    RxJavaHooks.reset();
    RxAndroidPlugins.getInstance().reset();
}

此代码必须包装Observable测试,因此可以如图所示在@Before@After中完成,可以将其放入JUnit @Rule中或放置在代码中的任何位置.只是不要忘记重置钩子.

This code has to wrap the Observable test, so it can be done within @Before and @After as shown, it can be put into JUnit @Rule or placed anywhere in the code. Just don't forget to reset the hooks.

第二个选项是通过依赖注入为类(Presenters,DAO)提供显式的Scheduler实例,然后再次使用Schedulers.immediate()(或其他适合测试的对象).

Second option is to provide explicit Scheduler instances to classes (Presenters, DAOs) through dependency injection, and again just use Schedulers.immediate() (or other suitable for testing).

如@aleien所指出的,您还可以使用执行Scheduler应用程序的注入的RxTransformer实例.

As pointed out by @aleien, you can also use an injected RxTransformer instance that executes Scheduler application.

我在生产中使用了第一种方法,效果很好.

I've used the first method with good results in production.

这篇关于使用改造和rxjava单元测试android应用程序的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆