How to handle mocked RxJava2 exception exception observed in unit test

I have been doing TDD in Kotlin for the past few weeks on Android using MVP. Everything goes well.

I am using Mockito to mock classes, but I cannot figure out how to implement one of the tests I wanted to run.

Below are my tests:

  • Call api, get a list of data and then show the list. loadAllPlacesTest()

  • Call api, get empty data and then show the list. loadEmptyPlacesTest()

  • Call api, some exceptions will happen on the path and then show an error message. loadExceptionPlacesTest()

I have tests for # 1 and # 2 successfully. The problem is # 3 , I'm not sure how to approach the test in code.

RestApiInterface.kt

interface RestApiInterface {

@GET(RestApiManager.PLACES_URL)
fun getPlacesPagedObservable(
        @Header("header_access_token") accessToken: String?,
        @Query("page") page: Int?
): Observable<PlacesWrapper>
}

      

RestApiManager.kt manager class that implements the interface looks like this:

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<PlacesWrapper> {
    //return throw Exception("sorry2")
    return api.getPlacesPagedObservable(
            accessToken,
            page)
}
}

      

}

Here is my unit test:

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>(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 This is the presenter.

   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<PlacesWrapper>() {
                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<String> {
    return object : DisposableObserver<String>() {
        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"))
        }
    }
}}

      

Problem / questions for loadExceptionPlacesTest()

  • I'm not sure why the code is not going to Presenter onError()

    . correct me if i am wrong, but this is what i think:
  •       a - `apiManager.getPlacesPagedObservable (" ", 0)` the observable itself throws an exception, so `.subscribe ()` cannot happen / continue, and the observer methods will not get called,
  •       b - it will only work in onError () when operations inside the observable encounter an Exception such as a JSONException
  1. For loadExceptionPlacesTest()

    I think 1b above is a way to make the presenter onError()

    receive the call and do the test pass. It's right? If so, then do it in the test. If this is not the case, you guys can point out what I am missing or something is wrong.
+3


source to share


1 answer


I'll leave it here for future reference and may develop a little more, although I answered in the comments.

What you are trying to accomplish is to put a stream into a stream onError

. Unfortunately, mocking this:

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

      

You are actually telling Mockito to set up your layout in such a way that just a call apiManager.getPlacesPagedObservable(anystring, anystring)

should have thrown an exception.

Indeed, throwing an exception from the Rx stream will stop the entire stream and terminate in the method onError

. However, this is exactly the problem you are working with. You are not on the thread when the exception is thrown.



Instead, you must tell Mockito that after the call, apiManager.getPlacesPagedObservable(anystring, anystring)

you want to return a stream that ends in onError

. This can be easily achieved with the Observable.error()

following:

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

      

(You may need to add some type information to this part here Observable.error()

, you may also need to use something else instead of an observable - single, completeable, etc.)

Mocking this would mean Mockito will tweak your layout to return an observable, which will be erroneous as soon as it is subscribed. This, in turn, will bring your subscriber directly to the stream onError

with the specified exception.

+4


source







All Articles