buddhabath buddhabath - 16 days ago 5
Android Question

Mockito Wanted but not Invoked

I'm new to writing tests and using Mockito.
I've read the similar topics here on Stackoverflow and made the suggested changes, making sure that regarded classes / interfaces / methods are open.


I tried to follow this


Mocking the constructor injected dependencies

This is the test I came up with so far

class RegistrationPresenterTest {
@Test
fun testRegisterSuccess() {
val mockService = mock<IHerokuInteractor>()
val mockLocal = mock<ILocalStorageInteractor>()
val mockView = mock<RegisterView>()
val mockRegistrationResponse = HerokuRegisterResponse("hash")
val mockPair = ImeiPair("imei","hash")
val presenter = RegisterPresenterImpl(mockLocal,mockService)

whenever(mockService.register(any())).thenReturn(Observable.just(mockRegistrationResponse))
whenever(mockLocal.clearPreferences()).thenReturn(Observable.just(true))
whenever(mockLocal.putImeiPair(any())).thenReturn(Observable.just(true))
//whenever(presenter.writeImeiPairLocally(any())) How do I specify parameters since it uses a parameter from the register method?

presenter.bindView(mockView)
presenter.register("imei","male")

verify(mockService, times(1)).register(any())
verify(mockLocal,times(1)).clearPreferences()
verify(mockLocal,times(1)).putImeiPair(any())
verify(mockView,times(1)).moveToMain()
}


but the response I keep getting is

Wanted but not invoked:
registerPresenterImpl.writeImeiPairLocally(
<any com.company.appname.model.ImeiPair>
);

Actually, there were zero interactions with this mock.


I got this response even when I don't mention that method in the test.
This is my presenter register method. I've changed the classes / interfaces & methods involved to open (kotlin). I believe override methods are open by nature in kotlin.

open class RegisterPresenterImpl @Inject constructor(val localStorage : ILocalStorageInteractor, var herokuService : IHerokuInteractor)

override fun register(imei : String, gender : String){
subscription = herokuService.register(RegisterObject(imei,gender)).subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(
{
registrationResult ->
Log.d(TAG,"${registrationResult}")
if(registrationResult.imei_hash != null){
writeImeiPairLocally(ImeiPair(imei,registrationResult.imei_hash))
}
else{
Log.e(TAG,"User already exists")
}
},
{
errorResponse -> Log.e(TAG,"Could not register user ${errorResponse.message}")
}
)
addSubscription(subscription)
}


and similarly the

open fun writeImeiPairLocally(pair : ImeiPair){
subscription = localStorage.clearPreferences().flatMap {
cleared -> localStorage.putImeiPair(pair)}.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread()).subscribe(
{

booleanResult -> view?.moveToMain()
},
{
errorResponse -> Log.e(TAG,"Could not write ImeiPair to SharedPreferences ${errorResponse.message}")
}
)
addSubscription(subscription)

}


Here is interfaces

open interface ILocalStorageInteractor : ILocalStorage{
fun getImeiPair() : Observable<ImeiPair>
fun putImeiPair(pair: ImeiPair) : Observable<Boolean>
}

open interface ILocalStorage {
fun clearPreferences() : Observable<Boolean>
}


All help is appreciated.

Answer

If you are using plain jUnit, then your AndroidSchedulers.mainThread() is null. That's why onNext is not called.

You need to override Schedulers in a setUp() method with:

    RxAndroidPlugins.getInstance().registerSchedulersHook(new RxAndroidSchedulersHook() {
        @Override
        public Scheduler getMainThreadScheduler() {
            return Schedulers.immediate(); // or .test()
        }
    });

To avoid concurrency in tests, I would recommend to override Schedulers.io() like this:

    RxJavaHooks.setOnIOScheduler(scheduler1 -> Schedulers.immediate());

If you are going to use TestScheduler, don't forget to call TestScheduler.triggerActions() method.

Also don't forget to unregister Schedulers in tearDown() like this:

    RxJavaHooks.reset();
    RxAndroidPlugins.getInstance().reset();
    AndroidSchedulers.reset();
    Schedulers.reset();
Comments