Android MVP架构下Presenter测试方法咨询(Mockito/Mockk、RxJava)
我来帮你一步步梳理如何测试这个Presenter,还有你提到的RxJava调度器的问题,以及测试范围的疑问~
测试MyPresenterImpl的实践指南
一、先明确测试目标
Presenter作为View和UseCase的中间层,我们的测试核心是验证:
- Presenter是否正确触发了View的对应方法(比如加载成功显示结果、失败显示错误)
- Presenter是否正确与UseCase交互(调用了execute方法)
- 订阅管理逻辑是否正常(比如取消订阅、生命周期处理)
二、哪些方法需要测试?
不是所有方法都要写测试用例,优先覆盖有业务逻辑或状态变化的方法:
- ✅
loadResults():核心业务方法,必须测成功、失败两种场景 - ✅
rxJavaUnsuscribe():订阅取消逻辑,要测已释放和未释放两种情况 - ⚙️
attachView()/detachView():如果只是简单的赋值+调用showProgressBar,可以选测(主要验证View方法是否被触发)
三、为什么要配置RxJava的Schedulers为Trampoline?
在Android环境中,RxJava默认用Schedulers.io()(异步IO线程)和AndroidSchedulers.mainThread()(主线程)执行任务,这会导致测试用例在异步任务完成前就结束,断言直接失败。
Schedulers.trampoline()的作用是让所有RxJava任务在当前线程同步执行,相当于把异步操作变成同步,这样测试就能按顺序跑完所有逻辑,不需要等待异步回调。
配置示例(JUnit 4)
在测试类的初始化和清理方法中全局替换调度器:
@Before fun setUp() { // 替换所有RxJava调度器为同步线程 RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() } RxJavaPlugins.setComputationSchedulerHandler { Schedulers.trampoline() } RxJavaPlugins.setNewThreadSchedulerHandler { Schedulers.trampoline() } // 替换Android主线程调度器 RxAndroidPlugins.setMainThreadSchedulerHandler { Schedulers.trampoline() } } @After fun tearDown() { // 测试结束后重置调度器,避免影响其他测试 RxJavaPlugins.reset() RxAndroidPlugins.reset() }
四、具体测试用例(Mockito + JUnit 4)
先准备好测试依赖:Mock核心类、被测试的Presenter,然后分场景写用例。
1. 测试attachView方法
验证绑定View时是否正确调用了进度条显示:
class MyPresenterImplTest { @Mock private lateinit var myUseCase: MyUseCase @Mock private lateinit var view: MyContract.View private lateinit var presenter: MyPresenterImpl @Before fun setUp() { // 初始化Mockito MockitoAnnotations.openMocks(this) // 配置RxJava同步调度器 RxJavaPlugins.setIoSchedulerHandler { Schedulers.trampoline() } RxAndroidPlugins.setMainThreadSchedulerHandler { Schedulers.trampoline() } // 创建Presenter实例 presenter = MyPresenterImpl(myUseCase) } @After fun tearDown() { RxJavaPlugins.reset() RxAndroidPlugins.reset() } @Test fun `attach view should trigger progress bar display`() { // 执行绑定操作 presenter.attachView(view) // 断言View的showProgressBar(true)被调用 verify(view).showProgressBar(true) // 验证View实例被正确持有 assertNotNull(presenter.mView) } }
2. 测试loadResults成功场景
验证UseCase返回成功结果时,Presenter正确更新View:
@Test fun `load results should show data when use case succeeds`() { // 准备测试数据 val testData = listOf("Result 1", "Result 2") // Mock UseCase的execute方法,返回成功的Observable `when`(myUseCase.execute()).thenReturn(Observable.just(testData)) // 先绑定View presenter.attachView(view) // 执行加载逻辑 presenter.loadResults() // 按顺序验证方法调用(确保进度条先隐藏,再显示结果) inOrder(view).apply { verify(view).showProgressBar(true) // attach时的调用 verify(view).showProgressBar(false) verify(view).showResults(testData) } }
3. 测试loadResults失败场景
验证UseCase抛出异常时,Presenter正确显示错误:
@Test fun `load results should show error when use case fails`() { // 准备测试异常 val testError = RuntimeException("Network error") // Mock UseCase返回错误的Observable `when`(myUseCase.execute()).thenReturn(Observable.error(testError)) presenter.attachView(view) presenter.loadResults() inOrder(view).apply { verify(view).showProgressBar(true) verify(view).showProgressBar(false) verify(view).showError(testError.localizedMessage) } }
4. 测试rxJavaUnsuscribe逻辑
验证未释放的订阅会被正确取消,已释放的不会重复取消:
@Test fun `unsubscribe should dispose active disposable`() { // Mock一个未释放的Disposable val mockDisposable = mock(Disposable::class.java) `when`(mockDisposable.isDisposed).thenReturn(false) // 给Presenter设置这个Disposable(如果是private字段,可以用反射或者改成internal) presenter.disposable = mockDisposable presenter.rxJavaUnsuscribe() // 验证dispose()被调用 verify(mockDisposable).dispose() } @Test fun `unsubscribe should not dispose already disposed disposable`() { val mockDisposable = mock(Disposable::class.java) `when`(mockDisposable.isDisposed).thenReturn(true) presenter.disposable = mockDisposable presenter.rxJavaUnsuscribe() // 验证dispose()从未被调用 verify(mockDisposable, never()).dispose() }
五、关于Mockk的小提示
你说Mockk没能成功运行,大概率是依赖配置或语法问题。比如Mockk需要引入io.mockk:mockk:x.y.z,而且Kotlin版本要兼容。如果换成Mockk,核心逻辑和上面一样,只是语法更简洁:
- 用
mockk<MyUseCase>()创建Mock实例 - 用
every { myUseCase.execute() } returns Observable.just(testData)设置返回值 - 用
verify { view.showResults(testData) }验证方法调用
最后总结
- 优先覆盖核心业务逻辑和状态变化的方法,不用为简单的get/set写测试
- 必须配置RxJava同步调度器,否则异步操作会导致测试失效
- 用Mock工具(Mockito/Mockk)隔离View和UseCase,只关注Presenter的逻辑
内容的提问来源于stack exchange,提问作者StuartDTO
相关产品推荐
相关产品推荐

