如何在unit testing中处理嘲讽的RxJava2可观察抛出exception

在过去的几个星期里,我一直在Kotlin使用MVP在Android上进行TDD。 事情进展顺利。

我使用Mockito来模拟类,但我似乎无法完成如何执行我想运行的测试之一。

以下是我的测试:

  1. 调用API,接收数据列表,然后显示列表。 loadAllPlacesTest()
  2. 调用API,接收空数据,然后显示列表。 loadEmptyPlacesTest()
  3. 调用API,在路上发生一些exception,然后显示错误信息。 loadExceptionPlacesTest()

我已经成功测试了#1和#2。 问题在于#3 ,我不确定如何在代码中进行测试。

RestApiInterface.kt

 interface RestApiInterface { @GET(RestApiManager.PLACES_URL) fun getPlacesPagedObservable( @Header("header_access_token") accessToken: String?, @Query("page") page: Int? ): Observable } 

RestApiManager.kt实现接口的管理器类如下所示:

 open class RestApiManager: RestApiInterface{ var api: RestApiInterface internal set internal var retrofit: Retrofit init { val logging = HttpLoggingInterceptor() // set your desired log level logging.setLevel(HttpLoggingInterceptor.Level.BODY) val client = okhttp3.OkHttpClient().newBuilder() .readTimeout(60, TimeUnit.SECONDS) .connectTimeout(60, TimeUnit.SECONDS) .addInterceptor(LoggingInterceptor()) .build() retrofit = Retrofit.Builder() .baseUrl(BASE_URL) .client(client) .addConverterFactory(GsonConverterFactory.create()) .addCallAdapterFactory(RxJava2CallAdapterFactory.create())//very important for RXJAVA and retrofit .build() api = retrofit.create(RestApiInterface::class.java) } override fun getPlacesPagedObservable(accessToken: String?, page: Int?): Observable { //return throw Exception("sorry2") return api.getPlacesPagedObservable( accessToken, page) } } 

}

这是我的unit testing:

 class PlacesPresenterImplTest : AndroidTest(){ lateinit var presenter:PlacesPresenterImpl lateinit var view:PlacesView lateinit var apiManager:RestApiManager //lateinit var apiManager:RestApiManager val EXCEPTION_MESSAGE1 = "SORRY" val MANY_PLACES = Arrays.asList(PlaceItem(), PlaceItem()); var EXCEPTION_PLACES = Arrays.asList(PlaceItem(), PlaceItem()); val manyPlacesWrapper = PlacesWrapper(MANY_PLACES) var exceptionPlacesWrapper = PlacesWrapper(EXCEPTION_PLACES) val emptyPlacesWrapper = PlacesWrapper(Collections.emptyList()) @After fun clear(){ RxJavaPlugins.reset() } @Before fun init(){ //MOCKS THE subscribeOn(Schedulers.io()) to use the same thread the test is being run on //Schedulers.trampoline() runs the test in the same thread used by the test RxJavaPlugins.setIoSchedulerHandler { t -> Schedulers.trampoline() } view = Mockito.mock(PlacesView::class.java) apiManager = Mockito.mock(RestApiManager::class.java) presenter = PlacesPresenterImpl(view,context(), Bundle(), Schedulers.trampoline()) presenter.apiManager = apiManager //exceptionPlacesWrapper = throw Exception(EXCEPTION_MESSAGE1); } @Test fun loadAllPlacesTest() { Mockito.`when`(apiManager.getPlacesPagedObservable(Mockito.anyString(), Mockito.anyInt())).thenReturn(Observable.just(manyPlacesWrapper)) presenter.__populate() Mockito.verify(view, Mockito.atLeastOnce()).__showLoading() Mockito.verify(view, Mockito.atLeastOnce())._showList() Mockito.verify(view).__hideLoading() Mockito.verify(view).__showFullScreenMessage(Mockito.anyString()) } @Test fun loadEmptyPlacesTest() { Mockito.`when`(apiManager.getPlacesPagedObservable(Mockito.anyString(), Mockito.anyInt())).thenReturn(Observable.just(emptyPlacesWrapper)) presenter.__populate() Mockito.verify(view, Mockito.atLeastOnce()).__showLoading() Mockito.verify(view, Mockito.atLeastOnce())._showList() Mockito.verify(view).__hideLoading() Mockito.verify(view).__showFullScreenMessage(Mockito.anyString()) } @Test fun loadExceptionPlacesTest() { Mockito.`when`(apiManager.getPlacesPagedObservable(Mockito.anyString(), Mockito.anyInt())).thenThrow(Exception(EXCEPTION_MESSAGE1)) presenter.__populate() Mockito.verify(view, Mockito.atLeastOnce()).__showLoading() Mockito.verify(view, Mockito.never())._showList() Mockito.verify(view).__hideLoading() Mockito.verify(view).__showFullScreenMessage(EXCEPTION_MESSAGE1) } } 

PlacesPresenterImpl.kt这是主持人。

  class PlacesPresenterImpl constructor(var view: PlacesView, var context: Context, var savedInstanceState:Bundle?, var mainThread: Scheduler) : BasePresenter(), BasePresenterInterface, PlacesPresenterInterface { lateinit var apiManager:RestApiInterface var placeListRequest: Disposable? = null override fun __firstInit() { apiManager = RestApiManager() } override fun __init(context: Context, savedInstanceState: Bundle, view: BaseView?) { this.view = view as PlacesView if (__isFirstTimeLoad()) __firstInit() } override fun __destroy() { placeListRequest?.dispose() } override fun __populate() { _callPlacesApi() } override fun _callPlacesApi() { view.__showLoading() apiManager.getPlacesPagedObservable("", 0) .subscribeOn(Schedulers.io()) .observeOn(mainThread) .subscribe (object : DisposableObserver() { override fun onNext(placesWrapper: PlacesWrapper) { placesWrapper?.let { val size = placesWrapper.place?.size view.__hideLoading() view._showList() System.out.println("Great I found " + size + " records of places.") view.__showFullScreenMessage("Great I found " + size + " records of places.") } System.out.println("onNext()") } override fun onError(e: Throwable) { System.out.println("onError()") //e.printStackTrace() view.__hideLoading() if (ExceptionsUtil.isNoNetworkException(e)){ view.__showFullScreenMessage("So sad, can not connect to network to get place list.") }else{ view.__showFullScreenMessage("Oops, something went wrong. ["+e.localizedMessage+"]") } this.dispose() } override fun onComplete() { this.dispose() //System.out.printf("onComplete()") } }) } private fun _getEventCompletionObserver(): DisposableObserver { return object : DisposableObserver() { override fun onNext(taskType: String) { //_log(String.format("onNext %s task", taskType)) } override fun onError(e: Throwable) { //_log(String.format("Dang a task timeout")) //Timber.e(e, "Timeout Demo exception") } override fun onComplete() { //_log(String.format("task was completed")) } } }} 

关于loadExceptionPlacesTest()

  1. 我不知道为什么代码不会去Presenter的onError()纠正我,如果我错了以下,但这是我的想法:
  • apiManager.getPlacesPagedObservable(“”,0)`observable自身抛出一个Exception,这就是为什么`.subscribe()`不能发生/继续,观察者的方法不会被调用,
  • b – 当observable内部的操作遇到像JSONException这样的Exception时,它只会进入onError()
  1. 对于loadExceptionPlacesTest()我认为上面的1b是让演示者的onError()被调用并使测试通过的方法。 它是否正确? 如果是在测试中如何去做。 如果不是,你们可以指出我失踪或做错了什么?

我将留在这里供将来参考,并能够详细阐述一点,即使我已经在评论中回答了

你试图完成的是把这个流放在onError流中。 不幸的是,通过这样嘲笑:

 Mockito.`when`(apiManager.getPlacesPagedObservable( Mockito.anyString(), Mockito.anyInt())) .thenThrow(Exception(EXCEPTION_MESSAGE1)) 

你实际上告诉Mockito设置你的模拟,只是调用apiManager.getPlacesPagedObservable(anystring, anystring)应该抛出一个exception。

在Rx流中抛出一个exception的确会导致整个流停止并最终在onError方法中。 但是,这正是您使用的方法的问题。 抛出exception时,您不在流内。

相反,你想要做的就是告诉Mockito,一旦你调用apiManager.getPlacesPagedObservable(anystring, anystring)你想返回一个流,最终会在onError 。 这可以通过Observable.error()来实现,就像这样:

 Mockito.`when`(apiManager.getPlacesPagedObservable( Mockito.a‌​nyString(), Mockito.anyInt())) .thenReturn(Observable.error( Exception(EXCEPTION_MESSAGE1))) 

这可能是你需要在这里添加一些types信息Observable.error() ,你也可能需要使用别的东西,而不是一个可观察的 – 单一的,可完成的等

上面的嘲讽会告诉Mockito设置你的模拟器返回一个观察器,一旦它订阅就会出错。 这将反过来将您的订阅者直接放在具有指定exception的onError流中。