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
代码中的第一个问题是您使用静态方法.这不是一个可测试的体系结构,至少不容易,因为它使模拟实现变得更加困难.要正确执行操作,而不是使用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)
实现您的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)
然后是实际的模拟和测试,例如:
@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方法的线程和所述工作线程之间发生竞争条件.通常它会导致测试方法在可观察执行完成之前返回.
通过提供符合测试的调度程序,可以轻松解决上述两个问题,并且几乎没有选项:
使用RxJavaHooks和RxAndroidPluginsAPI覆盖到任何呼叫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或代码的任何地方放置.只是不要忘记重置钩子.
第二种选择是Scheduler通过依赖注入为类(Presenters,DAO)提供显式实例,并再次使用Schedulers.immediate()(或其他适合于测试).
正如@aleien所指出的,您还可以使用RxTransformer执行Scheduler应用程序的注入实例.
我已经使用了第一种方法,在生产中取得了良好的效果.