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

Android单元测试:如何验证RxJava retryWhen调用次数及修复测试异常

这种问题我之前在写RxJava重试逻辑测试时也踩过坑!核心问题大概率是你的retryWhen延迟逻辑没有绑定到TestScheduler上,导致调用advanceTimeBy时,真正的延迟还是在真实线程里跑,而TestScheduler的时钟根本没在控制重试的等待时间。调试时因为你手动等待了真实时间,所以重试完成、断言通过,但自动化测试里时间没推进到位,订阅就还处于运行状态。

下面给你一步步排查和解决的方法:

1. 确保retryWhen的延迟逻辑绑定TestScheduler

首先,你的重试延迟(比如每次重试等待1秒)必须显式指定使用TestScheduler,而不是默认的Scheduler。比如原来的错误写法和修正后的正确写法对比:

错误示例(无法被TestScheduler控制)

// 扩展函数里的retryWhen逻辑
fun <T> Observable<T>.retryWithMaxAttempts(maxRetries: Int): Observable<T> {
    return retryWhen { attempts ->
        attempts.zipWith(Observable.range(1, maxRetries + 1)) { error, count ->
            if (count <= maxRetries) count else throw error
        }.flatMap {
            // 这里用了默认Scheduler,TestScheduler管不到
            Observable.timer(1, TimeUnit.SECONDS)
        }
    }
}

正确示例(绑定TestScheduler)

修改扩展函数,允许传入Scheduler参数,测试时传入TestScheduler:

fun <T> Observable<T>.retryWithMaxAttempts(maxRetries: Int, scheduler: Scheduler): Observable<T> {
    return retryWhen { attempts ->
        attempts.zipWith(Observable.range(1, maxRetries + 1)) { error, count ->
            if (count <= maxRetries) count else throw error
        }.flatMap {
            // 显式指定使用传入的Scheduler
            Observable.timer(1, TimeUnit.SECONDS, scheduler)
        }
    }
}

2. 测试代码的正确写法

针对你需要验证的两个场景,编写绑定TestScheduler的测试用例:

场景1:触发最大重试次数后失败

@Test
fun testRetryWhenMaxRetriesReached_Fails() {
    val testScheduler = TestScheduler()
    val attemptCounter = AtomicInteger(0)

    // 模拟每次都失败的API调用
    val failingApi = Observable.defer {
        Observable.error<Response<*>>(IOException("API failed")).doOnSubscribe {
            attemptCounter.incrementAndGet()
        }
    }

    val testObserver = failingApi
        .retryWithMaxAttempts(3, testScheduler) // 最多重试3次,总计4次调用
        .test()

    // 初始状态:第一次调用失败,等待重试
    testObserver.assertNotTerminated()
    assertEquals(1, attemptCounter.get())

    // 推进总时长3秒,触发全部3次重试
    testScheduler.advanceTimeBy(3, TimeUnit.SECONDS)

    // 验证订阅终止,且收到最终错误
    testObserver.assertTerminated()
    testObserver.assertError(IOException::class.java)
    assertEquals(4, attemptCounter.get()) // 1次初始调用 + 3次重试
}

场景2:前n次失败,第n+1次成功

@Test
fun testRetryWhenNthRetrySucceeds() {
    val testScheduler = TestScheduler()
    val attemptCounter = AtomicInteger(0)
    val successResponse = Response(200, null, null)

    // 模拟前2次失败,第3次成功的API调用
    val flakyApi = Observable.defer {
        attemptCounter.incrementAndGet().let { count ->
            if (count <= 2) Observable.error<Response<*>>(IOException("API failed"))
            else Observable.just(successResponse)
        }
    }

    val testObserver = flakyApi
        .retryWithMaxAttempts(3, testScheduler)
        .test()

    // 第一次调用失败
    testObserver.assertNotTerminated()
    assertEquals(1, attemptCounter.get())

    // 推进1秒,触发第一次重试(第二次调用,失败)
    testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
    assertEquals(2, attemptCounter.get())
    testObserver.assertNotTerminated()

    // 再推进1秒,触发第二次重试(第三次调用,成功)
    testScheduler.advanceTimeBy(1, TimeUnit.SECONDS)
    assertEquals(3, attemptCounter.get())

    // 验证订阅成功终止,收到正确响应
    testObserver.assertTerminated()
    testObserver.assertValue(successResponse)
}

3. 排查其他未绑定TestScheduler的异步操作

如果你的call扩展函数内部还有其他异步操作(比如API调用用了subscribeOn(Schedulers.io())),需要统一替换为TestScheduler,或者在测试前后用RxJava插件全局替换默认Scheduler:

@Before
fun setup() {
    val testScheduler = TestScheduler()
    // 替换所有默认Scheduler为TestScheduler
    RxJavaPlugins.setIoSchedulerHandler { testScheduler }
    RxJavaPlugins.setComputationSchedulerHandler { testScheduler }
    RxJavaPlugins.setNewThreadSchedulerHandler { testScheduler }
}

@After
fun teardown() {
    // 测试结束后恢复默认Scheduler
    RxJavaPlugins.reset()
}

为什么逐行调试能通过?

调试时你单步执行的过程中,真实时间已经走完了重试的延迟(比如1秒),所以重试逻辑自然完成,订阅终止。但自动化测试里没有真实等待,必须靠TestScheduler主动推进时间,没绑定的话就会一直处于等待状态,导致assertTerminated()失败。

内容的提问来源于stack exchange,提问作者Saehun Sean Oh

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.11 08:17:45