如何测试 Android 中的 Presenter MVP

10

我在尝试理解如何测试我的应用程序,我还在学习使用 mockito,我也看到了 mockk,但无法使它工作。这是我的 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
    }

}

我应该如何测试这个Presenter?我需要添加所有这些方法吗?

我试图使用mockito进行测试,但我也可以使用mockk

有些人告诉我要使用Schedulers并使用trampoline,但我不太清楚,能否有人提供一个示例或稍微解释一下?

3个回答

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)
        }
    }
    

规则解释。

我们需要创建自定义测试规则,因为AndroidSchedulers.mainThread()返回的默认调度程序 (当您在Presenter中编写.observeOn(AndroidSchedulers.mainThread())时) 是LooperScheduler的实例,并且依赖于JUnit测试中不可用的Android依赖项。

因此,在测试运行之前,我们需要使用不同的调度程序初始化RxAndroidPlugins。使用规则允许您在多个测试类之间重用初始化逻辑。


1
如果我正确理解了你的问题,那么你正在尝试理解如何使用单元测试(使用Mockito)来实现完整的MVP模式。
我编写了一个示例代码(显示书籍列表的应用程序),其中包含一个基本的MVP实现和一个JUnit测试用例,可以在这里找到:https://github.com/harneev/AndroidMVPkt 让我们在这里谈论一下类:
  1. ViewContract.kt - 定义方法的接口,指导愚蠢的视图执行操作。
  2. ModelContract.kt - 定义从数据库或服务器获取数据的方法的接口,这些数据将封装在实现类中。
  3. Presenter.kt - 处理所有业务逻辑,并通过注入为参数的具体视图和模型来编排此逻辑。

注意:Presenter作为常规类和业务逻辑编排器依赖于模型和视图。一些开发人员喜欢将Presenter引用添加到View接口中,但这种方式更加清晰。

现在,让我们来谈谈针对这个MVP设计的单元测试用例 (PresenterTest.kt)。

我使用mockito-kotlin作为模拟框架,以获得更好的Kotlin支持。

在这种情况下,我只添加了一个名为test if books are displayed()的测试用例,它模拟了ViewContractModelContract并初始化了Presenter。最后,Mockito.verify方法验证视图是否收到了由模型生成的书籍列表。

为了获得更好的单元测试用例,我总是将其分解为以下三个场景,如下所述:

// 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

希望这有所帮助。

你没有使用RxJava,我的问题是如何测试这个Presenter...比如如何测试attachView()、loadResult()、rxJavaUnsubscribe()、detachView()... - StuartDTO
@StuartDTO,你可以使用Robolectric来谷歌“disposable”和“observer”JUnit测试。但是我建议创建一个单元测试,执行模拟操作并返回可断言的内容。不需要测试某个类中的所有方法。 - Harneev
你能提供一个我的演示者的例子吗?这样我就可以在你的回答上添加赏金了。 - StuartDTO
你能帮我一下吗? - StuartDTO
@StuartDTO 我已经将我的git库的develop分支更新为rx java支持。虽然我不经常使用rx java,但我尝试添加了TrampolineSchedulerSimpleScheduler测试以更好地理解何时使用哪种。请查看测试用例,因为它具有模拟服务,当调用时将返回所需的值。 - Harneev
显示剩余2条评论

0
编写 Presenter 的单元测试,您应该: 1. 模拟 `myUseCase`:`private val myUseCase = mock()` 2. 在 Presenter 的构造函数中添加 `Schedulers.io()` 和 `AndroidSchedulers.mainThread()`,然后在创建用于测试的 Presenter 对象时,可以设置 `Schedulers.trampoline()`。
    class MyPresenterImpl @Inject constructor(
        val myUseCase: MyUseCase,
        val ioScheduler: Scheduler = Schedulers,
        val uiScheduler: Scheduler = AndroidSchedulers.mainThread()
    ) : MyContract.Presenter

然后在你的测试的setUp()中:

    presenter = MyPresenterImpl(myUseCase, Schedulers.trampoline(), Schedulers.trampoline())
  • 为用例的函数execute()创建桩(stub):
  •     myUseCase.stub {
            on { execute() }.thenReturn(Single.just(xyz))
        }
    
    1. 验证你的代码

    注:我更喜欢使用Nhaarman的Mockito版本。


    谢谢您的回答,但是为什么我需要在我的Presenter构造函数中添加这些参数呢? - StuartDTO
    你可以创建2个变量来代替添加这些参数,但是在创建presenter之后,你必须将Schedulers.trampoline()分配给这些变量。然后在你的函数中,你应该使用这些调度器subscribeOn(ioScheduler)observeOn(uiScheduler)。目的是在运行测试时使用Schedulers.trampoline()。 - Yoyo
    还有其他方法可以在不更改它的情况下进行测试吗? - StuartDTO
    @StuartDTO 你不想更改调度程序吗? - Yoyo
    我的意思是,在不触及Presenter本身的情况下,其他类无法完成这项工作吗? - StuartDTO
    你能帮我一下吗?:( - StuartDTO

    网页内容由stack overflow 提供, 点击上面的
    可以查看英文原文,
    原文链接