You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android MVP+Dagger+RxJava架构下LoginPresenter方法测试问题

解决LoginPresenter中requestAuthorization()的测试问题

首先咱们拆解下你遇到的核心痛点:你的Presenter里既有依赖注入的外部对象(AppDataStore、SchedulerProvider),又有内部初始化的固定变量(requireMobileLength、CompositeDisposable),再加上RxJava的异步调度特性,这些组合起来导致测试难以落地。下面我给你一步步讲怎么搞定:

1. 先准备好测试依赖

确保你Module级别的build.gradle里添加了这些常用测试库:

testImplementation 'junit:junit:4.13.2'
testImplementation 'org.mockito:mockito-core:4.5.1'
testImplementation 'io.reactivex.rxjava3:rxjava-test:3.1.5'
testImplementation 'androidx.arch.core:core-testing:2.2.0'

2. 给Presenter做个小调整(可选但更优雅)

你已经用构造注入处理了appRepo和scheduler,这点很棒。另外建议给Presenter的dropView()方法加上Disposable清空逻辑,方便测试结束后清理资源:

override fun dropView() {
    view = null
    disposable.clear()
}

如果后续需要测试不同的手机号长度规则,还可以把requireMobileLength改成带默认值的构造参数,比如:

LoginPresenter @Inject constructor(
    val appRepo: AppDataStore, 
    var scheduler: SchedulerProvider,
    val requireMobileLength: Int = 10
) : LoginContract.Presenter

3. 编写测试用例的核心思路

咱们要Mock所有外部依赖(AppDataStore、LoginContract.View),用测试专用的同步调度器替换RxJava的异步线程,这样就能同步验证方法的每一步行为。

示例测试代码

class LoginPresenterTest {
    // Mock外部依赖
    private val mockAppRepo: AppDataStore = mock(AppDataStore::class.java)
    private val mockView: LoginContract.View = mock(LoginContract.View::class.java)
    // 测试用调度器:让所有Rx任务同步执行
    private val testScheduler = TestScheduler()
    private val testSchedulerProvider = object : SchedulerProvider {
        override fun ui(): Scheduler = testScheduler
        override fun io(): Scheduler = testScheduler
        override fun computation(): Scheduler = testScheduler
    }

    private lateinit var presenter: LoginPresenter

    @Before
    fun setup() {
        // 初始化Presenter,传入Mock依赖和测试调度器
        presenter = LoginPresenter(mockAppRepo, testSchedulerProvider)
        presenter.takeView(mockView)
    }

    @After
    fun tearDown() {
        // 清理资源,保证测试用例独立
        presenter.dropView()
    }

    @Test
    fun `requestAuthorization with valid mobile should trigger repo call and show success`() {
        // 1. 准备测试数据
        val validMobile = "1234567890"
        val mockAuthResponse = AuthorizationResponse("token_123")
        // 2. 预设MockRepo的行为:调用authorization时返回成功Observable
        `when`(mockAppRepo.authorization(validMobile)).thenReturn(Observable.just(mockAuthResponse))

        // 3. 执行待测试方法
        presenter.requestAuthorization(validMobile)

        // 4. 手动触发所有Rx任务执行(因为用了TestScheduler)
        testScheduler.triggerActions()

        // 5. 验证行为是否符合预期
        verify(mockView).showLoading()
        verify(mockAppRepo).authorization(validMobile)
        verify(mockView).hideLoading()
        verify(mockView).onAuthorizationSuccess(mockAuthResponse)
        verifyNoMoreInteractions(mockView)
    }

    @Test
    fun `requestAuthorization with invalid mobile length should show error`() {
        // 1. 准备不符合长度要求的手机号
        val invalidMobile = "12345"

        // 2. 执行待测试方法
        presenter.requestAuthorization(invalidMobile)

        // 3. 验证错误提示,且不会调用Repo方法
        verify(mockView).showError("手机号长度必须为10位") // 提示语要和你Presenter里的一致
        verify(mockAppRepo, never()).authorization(anyString())
    }
}

4. 关键细节说明

  • Mock依赖:用Mockito模拟AppDataStore和View,完全控制它们的行为,不用依赖真实的远程/本地数据,测试更稳定。
  • 同步Rx调度:TestScheduler让所有异步任务变成同步执行,通过triggerActions()手动触发任务完成,避免测试中因异步导致的断言失败。
  • 内部变量处理:固定值requireMobileLength如果需要灵活测试,可以改成构造参数;CompositeDisposable通过dropView()在测试后清空,避免内存泄漏。

如果你的requestAuthorization()还有其他逻辑(比如本地缓存校验、数据转换),都可以用同样的思路:先预设Mock行为,执行测试方法,最后验证输出和调用链路。


内容的提问来源于stack exchange,提问作者Jongz Puangput

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.20 10:20:57