0

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.

Community
  • 1
  • 1
buddhabath
  • 664
  • 1
  • 10
  • 22

1 Answers1

1

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();
Alexander Perfilyev
  • 6,739
  • 2
  • 17
  • 30
  • Thank you for the response. I added the first two blocks of code in a Before method setUp() and the last block in the After tearDown(), however I still get the same response. Was this the correct way to implement the suggested changes? I've never done testing before. – buddhabath Nov 18 '16 at 15:02
  • Have you tried to use a debugger inside your onNext call from the `register(imei : String, gender : String)` function? – Alexander Perfilyev Nov 18 '16 at 15:20
  • After moving test, (it was in instrumental test), now instead I get the same error but for clearPreferences(), check the bottom of my original post. At first it complained about not Mocking Log.e() in my Presenter – buddhabath Nov 18 '16 at 15:43
  • What value is returned by `registrationResult.imei_hash`? Put a break-point on that line and run a test in debug mode. – Alexander Perfilyev Nov 18 '16 at 15:47
  • registrationResult.imei_hash has value "hash" as set by the mock. And inside writeImeiPairLocally the pair has value "imei","hash" – buddhabath Nov 18 '16 at 15:55
  • Something was weird, I stepped through the test and then the test passed. Now it passes when I run it too. Thank you for the help – buddhabath Nov 18 '16 at 16:05
  • @buddhabath, just pass the Scheduler in the Constructor, every time you build observables. So there is no need to abuse the RxJava hook. The Rx-Java-hook is being set for one JVM, so you should restart the VM for every unit test. – Sergej Isbrecht Nov 18 '16 at 21:04
  • @buddhabath, It looks like RxJavaSchedulersHook has been deprecated. Please have a look at the documentation https://github.com/ReactiveX/RxJava/wiki/Plugins – Sergej Isbrecht Nov 18 '16 at 21:07
  • Yeah that's why I used `RxJavaHooks`. – Alexander Perfilyev Nov 20 '16 at 13:44