Rxjava 2 on error is not called while unit testing - android

Rxjava 2 on error is not called while unit testing . I am using kotlin with rxJava 2 .
method
#SuppressLint("CheckResult")
override fun fetchFilms() {
view.showLoader(true)
disposable = getRetrofitInstance(false).create(API::class.java)
.fetchFilms()
.subscribeOn(io)
.observeOn(main)
.subscribeWith(object : DisposableObserver<Response>() {
override fun onComplete() {
}
override fun onNext(t: Response) {
println(t)
view.setAdapter(t?.results as List<ResultsItem>)
view.showLoader(false)
}
override fun onError(e: Throwable) {
e.printStackTrace()
view.showLoader(false)
view.showError(e.message.toString()) }
})
}
Test
#Test
fun fetchInvalidDataShouldThrowError(){
`when`(api.fetchFilms()).thenReturn(Observable.error(IOException()))
filmsPresenter.fetchFilms()
val subscriber = TestSubscriber<Response>()
subscriber.awaitTerminalEvent(5,TimeUnit.SECONDS)
verify(filmsView, times(1)).showLoader(true)
verify(filmsView).showError("t")
verify(filmsView).showLoader(false)
}

It seems like you're running your code asynchonously in tests as well.
You'd have to synchronise the schedulers you use before.
#Before
fun setupSchedulers() {
RxAndroidPlugins.setMainThreadSchedulerHandler { Schedulers.trampoline() }
RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() }
}
#Test
fun fetchInvalidDataShouldThrowError() {
`when`(api.fetchFilms()).thenReturn(Observable.error(IOException("t")))
filmsPresenter.fetchFilms()
verify(filmsView).showLoader(true)
verify(filmsView).showError("t")
verify(filmsView).showLoader(false)
}

I had pass repo to Presenter to have same instance API .
here are some changes i have to make
class RepoTest(val api : API) {
fun filmm() : Observable<Response> {
return api.fetchFilms()
}
}
presenter
class FlimsPresenter(private val view: GetFlimsContract.View, repo: RepoTest) : GetFlimsContract.Presenter
method
override fun fetchFilms() {
view.showLoader(true)
disposable = repot.filmm()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribeWith(object : DisposableObserver<Response>() {
override fun onComplete() {
}
override fun onNext(t: Response) {
println(t)
view.setAdapter(t?.results as List<ResultsItem>)
if (t?.results.isEmpty()) view.showEmptyView(true)
else view.showEmptyView(false)
view.showLoader(false)
}
override fun onError(e: Throwable) {
e.printStackTrace()
view.showLoader(false)
view.showError(e.message.toString())
}
})
}
Finally test
class TestSample {
#Mock
private lateinit var filmsPresenter: GetFlimsContract.Presenter
#Mock
private lateinit var filmsView: GetFlimsContract.View
private lateinit var api: API
#Mock
private lateinit var disposable: Disposable
#Mock
lateinit var repoTest: RepoTest
#Before
fun setup() {
RxAndroidPlugins.setMainThreadSchedulerHandler { scheduler -> Schedulers.trampoline() }
RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler -> Schedulers.trampoline() }
RxJavaPlugins.setIoSchedulerHandler { t -> Schedulers.trampoline() }
MockitoAnnotations.initMocks(this)
repoTest = Mockito.mock(RepoTest::class.java)
filmsView = Mockito.mock(GetFlimsContract.View ::class.java)
filmsPresenter = FlimsPresenter(filmsView,repoTest )
}
#Test
fun `should lod item into views`() {
Mockito.`when`(repoTest.filmm()).thenReturn(Observable.just(Response("","",7, listOf(ResultsItem()))))
filmsPresenter.fetchFilms()
Mockito.verify(filmsView).showLoader(true)
Mockito.verify(filmsView).setAdapter(anyList())
Mockito.verify(filmsView).showLoader(false)
}
#Test
fun `should show error if network call fails`(){
Mockito.`when`(repoTest.filmm()).thenReturn(Observable.error(Exception("t")))
filmsPresenter.fetchFilms()
Mockito.verify(filmsView).showLoader(true)
Mockito.verify(filmsView).showError("t")
Mockito.verify(filmsView).showLoader(false)
}
}

your actually creating two API here.
every time fetchFilms() on presenter gets called a new API is created. you can not mock the API in this way.
best guess to fix it is to pass API object as a method parameter to fetchFilms() in the presenter. this way you can decide to provide a real API or a mock one

Related

Test a usecase with multiple coroutines (withContext and suspend) does not fail where expected

I created an instrumental test for a use case which has two coroutines, before fetching data and after. I run a test on the usecase but the test passes before the suspend function is done.
Usecase:
class FetchAllUseCase : Observable<FetchAllUseCase.Listener>() {
interface Listener {
fun onFetched(obj: List<Obj>)
}
fun fetchObjs() {
GlobalScope.launch(Dispatchers.IO) {
withContext(NonCancellable) {
val objs: List<Obj> = getObjs()
notify(objs)
}
}
}
private suspend fun notify(objs: List<Objs>) {
withContext(Dispatchers.Main) {
for(listener in listeners){
listener.onFetched(objs)
}
}
}
private fun getObjss(): List<Obj> {
/// fetch objects functionality
}
Test:
class FetchAllUseCase Test: FetchAllUseCase .Listener {
private lateinit var fetchAllUseCase: FetchAllUseCase
#Before
fun setUp() {
fetchAllUseCase = FetchAllUseCase()
fetchAllUseCase.registerListener(this)
}
#After
fun tearDown() {
fetchAllUseCase.unregisterListener(this)
}
#ExperimentalCoroutinesApi
#Test
fun fetchObjss()= runBlocking{
fetchAllUseCase.fetchObjs()
}
override fun onFetched(objs: List<Objs>) {
fail() //does not fail the test
}
}
Test already passes before onFetched() is called.
fail() or any other assertEquals that should fail the test does not affect the test result.
If I run the test on the MainActivity , adding:
#Rule
#JvmField
val mMainActivityTestRule = ActivityTestRule(MainActivity::class.java)
I will get a message "Process crashed" , but the test already passed.
If I remove both coroutines, the test works as expected, i.e fails.
I tried runBlocking, runBlockingTest, TestCoroutineDispatcher
None solved the issue so far
Add Dispatchers as Dependencies
class FetchAllUseCase(
private val mainDispatcher : CoroutineDispatcher = Dispatchers.Main,
private val ioDispatcher : CoroutineDispatcher = Dispatchers.IO,
) : Observable<FetchAllUseCase.Listener>() {
interface Listener {
fun onFetched(obj: List<Obj>)
}
fun fetchObjs() {
GlobalScope.launch(ioDispatcher) {
withContext(NonCancellable) {
val objs: List<Obj> = getObjs()
notify(objs)
}
}
}
private suspend fun notify(objs: List<Objs>) {
withContext(mainDispatcher) {
for(listener in listeners){
listener.onFetched(objs)
}
}
}
private fun getObjss(): List<Obj> {
/// fetch objects functionality
}
Testing, Inject The TestCoroutineDispatcher and use runBlockingTest
class FetchAllUseCase Test: FetchAllUseCase .Listener {
private lateinit var fetchAllUseCase: FetchAllUseCase
private val testDispatcher = TestCoroutineDispatcher()
#Before
fun setUp() {
fetchAllUseCase = FetchAllUseCase(testDispatcher,testDispatcher)
fetchAllUseCase.registerListener(this)
}
#After
fun tearDown() {
fetchAllUseCase.unregisterListener(this)
}
#ExperimentalCoroutinesApi
#Test
fun fetchObjss()= runBlockingTest{
fetchAllUseCase.fetchObjs()
}
override fun onFetched(objs: List<Objs>) {
fail() //does not fail the test
}
If This Didn't work checkout this link Kotlin Coroutines in Android — Unit Test

Solution to pause and resume in RxJava similar to TestCoroutineScope

Full source code is available at : https://github.com/AliRezaeiii/StarWarsSearch-RxPaging
Here is my local unit test where I test a ViewModel while I am using Coroutines for networking :
#Test
fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
testCoroutineRule.runBlockingTest {
`when`(api.fetchShowList()).thenReturn(emptyList())
}
val repository = ShowRepository(dao, api, context, TestContextProvider())
testCoroutineRule.pauseDispatcher()
val viewModel = MainViewModel(repository)
assertThat(viewModel.shows.value, `is`(Resource.loading()))
testCoroutineRule.resumeDispatcher()
assertThat(viewModel.shows.value, `is`(Resource.success(emptyList())))
}
As you know I can pause and resume using TestCoroutineScope, so I can test when liveData is in Loading or Success state.
I wonder if we can do the same thing when we test while we are using RxJava.
At the moment I just can verify Success state :
#Test
fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
`when`(repository.getSpecie(anyString())).thenReturn(Single.just(specie))
`when`(repository.getPlanet(anyString())).thenReturn(Single.just(planet))
`when`(repository.getFilm(anyString())).thenReturn(Single.just(film))
viewModel = DetailViewModel(schedulerProvider, character,
GetSpecieUseCase(repository), GetFilmUseCase(repository))
viewModel.liveData.value.let {
assertThat(it, `is`(notNullValue()))
if (it is Resource.Success) {
it.data?.let { data ->
assertTrue(data.films.isNotEmpty())
assertTrue(data.species.isNotEmpty())
}
}
}
}
in ViewModel init block, I send the network request. You can review it in the bellow class. That can be tested using pause and resume while using Coroutines. How about RxJava?
open class BaseViewModel<T>(
private val schedulerProvider: BaseSchedulerProvider,
private val singleRequest: Single<T>
) : ViewModel() {
private val compositeDisposable = CompositeDisposable()
private val _liveData = MutableLiveData<Resource<T>>()
val liveData: LiveData<Resource<T>>
get() = _liveData
init {
sendRequest()
}
fun sendRequest() {
_liveData.value = Resource.Loading
singleRequest.subscribeOn(schedulerProvider.io())
.observeOn(schedulerProvider.ui()).subscribe({
_liveData.postValue(Resource.Success(it))
}) {
_liveData.postValue(Resource.Error(it.localizedMessage))
Timber.e(it)
}.also { compositeDisposable.add(it) }
}
override fun onCleared() {
super.onCleared()
compositeDisposable.clear()
}
}
Without seeing what you tried, I can only guess there were two possible issues that required fixing:
Use the same TestScheduler for all provider methods:
class ImmediateSchedulerProvider : BaseSchedulerProvider {
val testScheduler = TestScheduler()
override fun computation(): Scheduler = testScheduler
override fun io(): Scheduler = testScheduler
override fun ui(): Scheduler = testScheduler
}
The unit tests weren't failing for the wrong state so they appear to pass even when the code hasn't run:
#Test
fun givenServerResponse200_whenFetch_shouldReturnSuccess() {
`when`(repository.getSpecie(anyString())).thenReturn(Single.just(specie))
`when`(repository.getPlanet(anyString())).thenReturn(Single.just(planet))
`when`(repository.getFilm(anyString())).thenReturn(Single.just(film))
viewModel = DetailViewModel(schedulerProvider, character, GetSpecieUseCase(repository),
GetPlanetUseCase(repository), GetFilmUseCase(repository))
viewModel.liveData.value.let {
assertThat(it, `is`(Resource.Loading))
}
schedulerProvider.testScheduler.advanceTimeBy(1, TimeUnit.MILLISECONDS) // <-------------
viewModel.liveData.value.let {
assertThat(it, `is`(notNullValue()))
if (it is Resource.Success) {
it.data?.let { data ->
assertTrue(data.films.isNotEmpty())
assertTrue(data.species.isNotEmpty())
}
} else {
fail("Wrong type " + it) // <---------------------------------------------
}
}
}

Issue with PublishSubject and TestScheduler, item isn't emitted

I have been facing an issue with subjects and TestSchedulers. My tests pass if I use a Trampoline scheduler but for some reason they fail if I use the TestScheduler.
Here's my sample test and relevant classes.
#RunWith(MockitoJUnitRunner::class)
class DemoViewModelTest {
//Error Mocks
private val actionsStream: PublishSubject<DemoContract.ViewEvent> = PublishSubject.create()
private lateinit var viewModel: DemoViewModel
private val handler = mock(DemoContract.Handler::class.java)
#Before
fun setup() {
viewModel = DemoViewModel(schedulersProvider, handler)
viewModel.viewEventsStream = actionsStream
}
#Test
fun testUpdateCounter() {
actionsStream.onNext(DemoContract.ViewEvent.UpdateClick)
testScheduler.triggerActions()
verify(handler).onUpdate()
}
protected var testScheduler = TestScheduler()
protected var schedulersProvider: SchedulersProvider = object : SchedulersProvider() {
override fun mainThread(): Scheduler {
return testScheduler
}
override fun io(): Scheduler {
return testScheduler
}
override fun computation(): Scheduler {
return testScheduler
}
override fun newThread(): Scheduler {
return testScheduler
}
override fun trampoline(): Scheduler {
return testScheduler
}
override fun single(): Scheduler {
return testScheduler
}
}
}
And my ViewModel class
class DemoViewModel (val schedulersProvider: SchedulersProvider, val handler:DemoContract.Handler) : DemoContract.ViewModel() {
var viewEventsStream: Observable<DemoContract.ViewEvent>? = null
set(value) {
field = value
subscribeToViewEvents()
}
private fun subscribeToViewEvents() {
viewEventsStream?.let {
it.subscribeOn(schedulersProvider.io())
.observeOn(schedulersProvider.mainThread())
.subscribe(object:Observer<DemoContract.ViewEvent>{
override fun onComplete() {
}
override fun onSubscribe(d: Disposable) {
}
override fun onNext(t: DemoContract.ViewEvent) {
onViewEvent(t)
}
override fun onError(e: Throwable) {
}
})
}
}
fun onViewEvent(event: DemoContract.ViewEvent) {
when (event) {
is DemoContract.ViewEvent.UpdateClick -> {
handler.onUpdate()
}
}
}
}
and My Contract class is
interface DemoContract {
abstract class ViewModel
sealed class ViewEvent {
object UpdateClick : ViewEvent()
}
interface Handler{
fun onUpdate()
}
}
A few things about this, If I replace
viewModel.viewEventsStream = actionsStream
with
viewModel.viewEventsStream = Observable.just(DemoContract.ViewEvent.Update)
this test passes.
Can someone throw some light on this, thanks in advance. Here's the minimum runnable project
Thanks a lot #akarnokd for pointing me in the right direction. Turns out this is a race condition between the subscriber for the PublishSubject being added(subscribeActual call) and the onNext Call that I had trigger in my test. onNext call returns before the former happens.
The solution is to explicitly invoke triggerActions twice once immediately after subscription and once after emission.
Change this
#Before
fun setup() {
viewModel = DemoViewModel(schedulersProvider, handler)
viewModel.viewEventsStream = actionsStream
}
To
#Before
fun setup() {
viewModel = DemoViewModel(schedulersProvider, handler)
viewModel.viewEventsStream = actionsStream
testScheduler.triggerActions()
}

However, there was exactly 1 interaction with this mock:

This is the first time I use mockito with kotlin mvp Rx
And, I got an error "wanted but not invoked - However, there was exactly 1 interaction with this mock"
Here my presenter class
class MatchPresenter(private val matchContract: MatchContract,private val apiService: MatchService,private val appSchedulerProvider: AppSchedulerProvider) {
fun getLastMatch() {
apiService
.getLastMatches()
.subscribeOn(appSchedulerProvider.newThread())
.observeOn(appSchedulerProvider.ui())
.subscribe(
{ matchList ->
matchContract.onSuccess(matchList)
},
{ error ->
matchContract.onFailed(error.message)
}
)
}
fun getNextMatch() {
apiService
.getNextMatches()
.subscribeOn(Schedulers.newThread())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(
{ matchList ->
matchContract.onSuccess(matchList)
},
{ error ->
matchContract.onFailed(error.message)
}
)
}
Here my presenter view
interface MatchContract {
fun onFailed(message: String?)
fun onSuccess(matchModel: MatchModel)
}
Here my AppSchedulerProvider
class AppSchedulerProvider : SchedulerProvider {
override fun trampoline(): Scheduler {
TODO("not implemented") //To change body of created functions use File | Settings | File Templates.
}
override fun newThread(): Scheduler {
return Schedulers.newThread()
}
override fun ui(): Scheduler {
return AndroidSchedulers.mainThread()
}
override fun computation(): Scheduler {
return Schedulers.computation()
}
override fun io(): Scheduler {
return Schedulers.io()
}
}
and here my presenterTest
class MatchPresenterTest {
#Mock
private lateinit var view:MatchContract
#Mock
private lateinit var apiService: MatchService
#Mock
private lateinit var appSchedulerProvider: AppSchedulerProvider
private lateinit var presenter : MatchPresenter
#Before
fun setup(){
MockitoAnnotations.initMocks(this)
RxAndroidPlugins.getInstance().registerSchedulersHook(object : RxAndroidSchedulersHook() {
override fun getMainThreadScheduler(): Scheduler {
return Schedulers.immediate() // or .test()
}
})
presenter = MatchPresenter(view,apiService,appSchedulerProvider)
}
#Test
fun getLastMatch_shoul_callSucces(){
val event: MutableList<EventModel> = mutableListOf()
val response = MatchModel(event)
RxJavaHooks.setOnIOScheduler { scheduler1 -> Schedulers.immediate() }
Mockito.`when`(apiService.getLastMatches()
).thenReturn(Observable.just(response))
presenter.getLastMatch()
verify(view).onSuccess(response)
verify(view).onFailed("failed")
}
}
Remove the verification for view.onFailed in the last line of your test. Other than that, you did everything right.

RxAndroid called from main thread

I have this issue ;)
I'm trying to call this usecase, which at the end returns an Observable.
But, despite using schedulers, constantly is being invoked on the main thread. I don't know why:
It looks like this:
class MainViewModel #Inject constructor(private val loadNewsUseCase: LoadNews) : Model {
override fun loadNews() {
loadNewsUseCase.execute(NewsObserver(), "")
}
override fun dispose() {
loadNewsUseCase.dispose()
}
}
class NewsObserver : DisposableObserver<Result>() {
override fun onComplete() {
Log.i("TAG", "")
}
override fun onNext(t: Result) {
Log.i("TAG", "")
}
override fun onError(e: Throwable) {
Log.i("TAG", "")
}
}
-
abstract class UseCase<T, in P>(
private val computationThreadExecutor: ComputationThreadExecutor,
private val mainThreadExecutor: MainThreadExecutor,
private val compositeDisposable: CompositeDisposable = CompositeDisposable()
) {
abstract fun createUseCase(params: P): Observable<T>
fun execute(disposableObserver: DisposableObserver<T>, params: P) {
requireNotNull(disposableObserver)
val observable = createUseCase(params)
.subscribeOn(Schedulers.newThread())
.observeOn(mainThreadExecutor.getThread())
val disposable = observable.subscribeWith(disposableObserver)
addDisposable(disposable)
}
private fun addDisposable(disposable: Disposable) {
requireNotNull(disposable)
compositeDisposable.add(disposable)
}
fun dispose() {
!compositeDisposable.isDisposed.apply { compositeDisposable.dispose() }
}
}
UseCase concrete implementation uses DataService to fetch data from api, which looks like this:
open class NewsDataService(private val newsDataProvider: NewsDataProvider) : NewsService {
override fun loadNews(): Observable<Result> {
return Observable.just(newsDataProvider.fetchData())
}
}
Inside NewsDataProvider is normal sync retrofit call.
Problem is, that from every begininning useCase is invoked in the mainThread(). Shouldn't be called in a new thread?
Replace
Observable.just(foo)
with something like
Observable.fromCallable(() -> foo)
Observable.just() creates an observable from the supplied values and you're computing the value on the main thread. fromCallable() takes in a callback that can be invoked on your subscription thread.
Make fetchData return an Observable<Result> (or potentially Single but that would require wider updates in your code). Retrofit supports RxJava.

Categories

Resources