Android MVP+Dagger+RxJava架构下LoginPresenter方法测试问题
首先咱们拆解下你遇到的核心痛点:你的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
相关产品推荐
相关产品推荐

