单元测试android应用程序与改造和rxjava

FVo*_*Vod 17 android mocking mockito rx-java retrofit2

我已经开发了一个使用rxJava改造的Android应用程序,现在我正在尝试使用Mockito设置单元测试,但我不知道如何模拟api响应以创建不做真正的测试电话但有假响应.

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

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();
            }
        }
    });
}
}
Run Code Online (Sandbox Code Playgroud)

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

}
Run Code Online (Sandbox Code Playgroud)

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

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



}
}
Run Code Online (Sandbox Code Playgroud)

您对我应该如何模拟和验证api响应有任何想法吗?提前致谢!

编辑:在@invariant建议之后,现在我将客户端对象传递给我的演示者,并且该api返回一个Observable而不是Subscription.但是,在进行api调用时,我的订阅服务器上出现了NullPointerException.测试类看起来像:

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();
}
}
Run Code Online (Sandbox Code Playgroud)

为什么我得到NullPointerException?

非常感谢!

mac*_*usz 30

如何测试RxJava和Retrofit

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

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

public class SplashPresenterImpl implements SplashPresenter {

private SplashView splashView;
private final ApiService service;

public SplashPresenterImpl(SplashView splashView, ApiService service) {
    this.splashView = splashView;
    this.apiService = service;
}
Run Code Online (Sandbox Code Playgroud)

2.创建测试类

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

public class SplashPresenterImplTest {

@Mock
ApiService apiService;

@Mock
SplashView splashView;

private SplashPresenter splashPresenter;

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

3.模拟和测试

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

@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()
}
Run Code Online (Sandbox Code Playgroud)

这样你就可以测试你的Observable + Subscription,如果你想测试Observable的行为是否正确,请用一个实例订阅它TestSubscriber.


故障排除

使用RxJava和RxAndroid调度程序进行测试时Schedulers.io(),AndroidSchedulers.mainThread()您可能会遇到运行observable/subscription测试的几个问题.

空指针异常

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

.observeOn(AndroidSchedulers.mainThread()) // throws NPE
Run Code Online (Sandbox Code Playgroud)

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

比赛条件

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

解决方案

通过提供符合测试的调度程序,可以轻松解决上述两个问题,并且几乎没有选项:

  1. 使用RxJavaHooksRxAndroidPluginsAPI覆盖到任何呼叫Schedulers.?AndroidSchedulers.?,迫使可观察使用,例如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();
    }
    
    Run Code Online (Sandbox Code Playgroud)

    此代码具有包住可观察测试,因此它可以内完成@Before并且@After如图所示,它可以放入的JUnit @Rule或代码的任何地方放置.只是不要忘记重置钩子.

  2. 第二种选择是Scheduler通过依赖注入为类(Presenters,DAO)提供显式实例,并再次使用Schedulers.immediate()(或其他适合于测试).

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

我已经使用了第一种方法,在生产中取得了良好的效果.

  • 对于错误测试,您可以使用`Observable.error(Throwable throwable)`工厂方法,例如`when(apiService.syncGenres()).thenReturn(new RuntimeException());`.如果您想更具体地了解错误,请使用retrofit的`HttpException`. (2认同)