10

I'm trying to understand how should I test my app, I'm still learning with mockito I also saw mockk but couldn't make it work, this is my Presenter

class MyPresenterImpl @Inject constructor(var myUseCase: MyUseCase) : MyContract.Presenter {

    private var mView: MyContract.View? = null
    private var disposable: Disposable? = null


    override fun attachView(view: MyContract.View) {
        this.mView = view
        this.mView?.showProgressBar(true)
    }

    override fun loadResults() {

        disposable = getList.execute()
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread())
            .subscribe(
                { result ->
                    mView?.showProgressBar(false)
                    mView?.showResults(result)
                },
                { error ->
                    mView?.showProgressBar(false)
                    mView?.showError(error.localizedMessage)
                })
    }

    override fun rxJavaUnsuscribe() {
        if (disposable != null && !disposable!!.isDisposed) {
            disposable!!.dispose()
        }
    }

    override fun detachView() {
        this.mView = null
    }

}

How I'm supposed to test this presenter? Do I have to add all of these methods?

I was trying to do it with mockito but I also can use mockk.

And some people told me that I have to do something with Schedulers and use the trampoline one but it's not clear for me could anyone of you provide an example or explain it a little bit?

StuartDTO
  • 783
  • 7
  • 26
  • 72

3 Answers3

1

If I understood your question correctly then you're trying to understand how to implement a complete MVP patter with Unit tests (using Mockito).

I've written a sample code (app which displays list of books) which explains a basic MVP implementation of same with one JUnit test case here : https://github.com/harneev/AndroidMVPkt

Lets talk a little about classes here:

  1. ViewContract.kt - Interface defining methods which guide dumb views to perform an action.
  2. ModelContract.kt - Interface defining methods to fetch data (either from database or from server) which will be encapsulated under implementation class.
  3. Presenter.kt - Handles all business logic and orchestrate this logic through concrete View and Model injected as parameters.

Note: Presenter being a regular class and business logic orchestrator is dependent on both model and views. Some developers like to add Presenter reference to View interface but it is cleaner this ways.

Now coming to unit test cases (PresenterTest.kt) for this MVP design.

I'm using mockito-kotlin as mocking framework for better kotlin support.

I've only added one test case in this scenario named test if books are displayed() which mocks ViewContract and ModelContract and initializes Presenter. Finally Mockito.verify method verifies if view received the list of books generated by model.

For better unit test cases I always break it down to following three scenarios which are explained as below:

// 1. given
how mocks will behave when their respective methods are called
// 2. when
when execution method is called
// 3. then
verify / assert if required action is performed

Hope this helps.

Harneev
  • 544
  • 4
  • 12
  • You're not using RxJava, my problem is how to test this presenter... like how to test the attachView(), loadResult(), rxJavaUnsuscribe(), dettachView()... – StuartDTO May 18 '19 at 10:36
  • @StuartDTO you can google disposable and observer junit tests using roboelectric. But what I'll suggest to create a unit test which performs a mock operation and returns something which you can assert. Need not to test all methods in a certain class. – Harneev May 19 '19 at 02:39
  • Can you provide an example of my presenter so I can add the bounty on your answer please? – StuartDTO May 19 '19 at 18:15
  • Can you help me on this please? – StuartDTO May 20 '19 at 16:23
  • @StuartDTO I have updated my git library `develop` branch with rx java support. I dont work on rx java much but I've tried to add a test `TrampolineScheduler` and `SimpleScheduler` for better understanding as when to use which. Have a look at test case as it has mock service which will return desired value when called upon. – Harneev May 21 '19 at 02:54
  • Ok, the thing with trampoline I did it using another approach, because I'm using dagger2, well the thing is how do I test attachView, dettachView, rxJavaUnsuscribe... do you know? – StuartDTO May 21 '19 at 18:05
  • As per my understanding they need not to be tested. As View and disposable objects will be mocked in your unit test. Concrete implementation of these objects like your main presenter should handle attach-view and unsubscribe methods. As per your code, unit test should only be concerned if there is an IO i.e. input and output. – Harneev May 21 '19 at 18:10
1
  1. Create custom rule TestSchedulerRule.kt for junit tests in your test package

    class TestSchedulerRule(private val scheduler: Scheduler = Schedulers.trampoline()) : TestRule {
            override fun apply(base: Statement, d: Description): Statement {
                return object : Statement() {
                     override fun evaluate() {
                         RxJavaPlugins.setIoSchedulerHandler { scheduler }
                         RxJavaPlugins.setComputationSchedulerHandler { scheduler }
                         RxJavaPlugins.setNewThreadSchedulerHandler { scheduler }
                         RxJavaPlugins.setSingleSchedulerHandler { scheduler }
                         RxAndroidPlugins.setInitMainThreadSchedulerHandler { scheduler }
                         RxAndroidPlugins.setMainThreadSchedulerHandler { scheduler }
    
                         try {
                             base.evaluate()
                         } finally {
                             RxJavaPlugins.reset()
                             RxAndroidPlugins.reset()
                         }
                     }
                } 
          }
    }
    
  2. Create MyPresenterImplTest for your presenter and write unit-tests you needed with created rule. For example i added one test for presenter logic with kotlin-mockito and junit4.

    @RunWith(MockitoJUnitRunner::class)
    class MyPresenterImplTest {
    
        @Rule
        @JvmField
        val testRule = TestSchedulerRule()
    
        private val view: MyContract.View = mock()
        private val myUseCase: MyUseCase = mock()
    
        private val presenter = MyPresenterImpl(myUseCase)
    
        @Before
        fun setUp() {
            presenter.attachView(view)
        }
    
        @Test
        fun `WHEN btnLoad clicked EXPECT load and show results`() {
            //create needed results
            val results = listOf<Any>()
    
           //mock the result of execution myUseCase.invoke()
            whenever(myUseCase.invoke()).thenReturn(Single.just(results))
    
           //trigger the needed action
            presenter.loadResults()
    
            //verify that all needed actions were called
            verify(myUseCase).invoke()
            verify(view).showResults(results)
        }
    }
    

Explanation about Rule.

We need to create custom test rule because the default scheduler returned by AndroidSchedulers.mainThread() (when you write .observeOn(AndroidSchedulers.mainThread() in your presenter) is an instance of LooperScheduler and relies on Android dependencies that are not available in JUnit tests.

So we need initializing RxAndroidPlugins with a different Scheduler before the tests runs. Using rule allows you to reuse the initialization logic across multiple test classes.

MrKovalev
  • 19
  • 4
0

To write a unit test for your presenter, you should: 1. mock myUseCase: private val myUseCase = mock<MyUseCase>() 2. addSchedulers.io()andAndroidSchedulers.mainThread()to the constructor of the presenter then you can setSchedulers.trampoline()` when you create a presenter object for testing:

    class MyPresenterImpl @Inject constructor(
        val myUseCase: MyUseCase,
        val ioScheduler: Scheduler = Schedulers,
        val uiScheduler: Scheduler = AndroidSchedulers.mainThread()
    ) : MyContract.Presenter

then in the seUp()of your test:

    presenter = MyPresenterImpl(myUseCase, Schedulers.trampoline(), Schedulers.trampoline())
  1. stub the function execute() of the use case:
    myUseCase.stub {
        on { execute() }.thenReturn(Single.just(xyz))
    }
  1. verify your code

notes: I prefer to use the Mockito version of Nhaarman

Yoyo
  • 165
  • 2
  • 4
  • Thanks for your answer, but why I have to add these parameters to my constructor of the presenter? – StuartDTO May 12 '19 at 18:09
  • You can create 2 variables instead of adding those parameter but you have to assign Schedulers.trampoline() to those variables after you created the presenter. Then in your function you should use those schedulers `subscribeOn(ioScheduler)` and `observeOn(uiScheduler)`. The purpose is to use Schedulers.trampoline() when run tests – Yoyo May 13 '19 at 00:26
  • And there's no other way to test without changing it? – StuartDTO May 13 '19 at 07:44
  • @StuartDTO you don't want to change schedulers? – Yoyo May 14 '19 at 03:05
  • I mean couldn't be done in other classes without touching the presenter itself? – StuartDTO May 14 '19 at 07:46
  • Can you help me on this please? :( – StuartDTO May 20 '19 at 16:23