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

使用Kotlin协程的Retrofit请求单元测试问题排查

解决Kotlin协程+Retrofit单元测试中Listener未被调用的问题

从你的代码和报错信息来看,测试失败的核心原因是协程在测试环境中没有被正确执行或等待,导致listener的回调还没触发,测试就已经结束了。下面是具体的问题分析和修复步骤:

1. 替换Main Dispatcher为测试专用Dispatcher

你的Interactor中使用了launch(UI)(也就是现在的Dispatchers.Main),但在单元测试环境中,Main dispatcher没有默认的执行线程,协程会被挂起而不执行。你需要在测试中替换它为Test Dispatcher:

首先确保添加了kotlinx-coroutines-test依赖,然后在测试类中配置:

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.test.TestDispatcher
import kotlinx.coroutines.test.UnconfinedTestDispatcher
import kotlinx.coroutines.test.resetMain
import kotlinx.coroutines.test.setMain
import org.junit.After
import org.junit.Before

class WorklistInteractorTest {
    private val testDispatcher: TestDispatcher = UnconfinedTestDispatcher()

    @Before
    fun setup() {
        Dispatchers.setMain(testDispatcher)
    }

    @After
    fun teardown() {
        Dispatchers.resetMain()
    }

    // 你的测试方法...
}

2. 使用runTest而非runBlocking执行测试

runBlocking不会自动等待所有协程完成,而runTest(来自kotlinx-coroutines-test)是专门为协程测试设计的,它会确保所有调度的协程都执行完毕:

@Test
fun `when worklistquery returns result, pass result back through listeners onWorklistResult`() = runTest {
    whenever(mWorklistRepositoryMock.getWorklist(anyString(), anyInt()))
        .thenReturn(Calls.response(expectedWorklistResult))

    mInteractor.getWorklist(mOnWorklistResultListenerMock)

    verify(mOnWorklistResultListenerMock).onWorklistResult(expectedWorklistResult)
    verifyNoMoreInteractions(mOnWorklistResultListenerMock)
}

3. 移除不必要的async包裹

你的Interactor代码中,launch里面再套一层async是多余的,这会增加协程层级,也可能导致测试中等待逻辑混乱。可以简化代码:

override fun getWorklist(listener: OnWorklistResultListener) {
    job = launch(Dispatchers.Main) { // 建议用Dispatchers.Main而非过时的UI
        val result = worklistRepository.getWorklist(
            ip = preferenceManager.worklistIp,
            port = preferenceManager.worklistPort
        ).awaitResult()

        when (result) {
            is Result.Ok -> listener.onWorklistResult(result.value)
            is Result.Error -> {
                Timber.e(result.exception, "HTTP error with code %s}", result.exception.code())
                when(result.exception.code()) {
                    401 -> listener.onInvalidCredentialsFailure()
                    500 -> listener.internalServerError()
                    503 -> listener.noServerResponseFailure()
                    else -> listener.onError(result.exception.cause.toString())
                }
            }
            is Result.Exception -> {
                Timber.e(result.exception.cause, "Exception with cause %s", result.exception.cause.toString())
                when(result.exception) {
                    is ConnectException -> listener.connectionRefused()
                    is SocketTimeoutException -> listener.failedToConnectToHost()
                    else -> listener.onError(result.exception.cause.toString())
                }
            }
        }
    }
}

4. 确保Retrofit Call的Mock正确

确认你的mWorklistRepositoryMock.getWorklist返回的Call在调用awaitResult()时会生成Result.Ok。如果用的是Retrofit的Calls.response,需要确保它是成功的响应,能正确解析成你的expectedWorklistResult对象。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 07:12:59