Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to test a presenter MVP Android

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?

like image 419
StuartDTO Avatar asked May 09 '19 15:05

StuartDTO


2 Answers

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.

like image 53
Harneev Avatar answered Nov 10 '22 08:11

Harneev


  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.

like image 27
MrKovalev Avatar answered Nov 10 '22 09:11

MrKovalev