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

如何在CoroutineScope中使用MockWebServer成功Mock Retrofit调用?

问题分析

你遇到的核心问题是:ViewModel里用CoroutineScope(Dispatchers.IO).launch创建的协程,脱离了测试用runTest调度器的控制——测试方法执行完毕时,协程还没来得及执行execute()代码块,导致你看不到运行结果。而去掉CoroutineScope时,代码在主线程同步执行,自然能正常跑完。

解决方案

下面提供两种可行的解决方式,按需选择:

方案1:给ViewModel注入CoroutineScope(推荐)

业务代码里不要硬编码创建CoroutineScope,而是通过构造函数注入,这样测试时可以传入受测试调度器管理的作用域,从根源上让协程受测试控制。

修改ViewModel代码

class YourViewModel(
    private val sensorsService: SensorsService,
    // 注入协程作用域,默认用Android官方推荐的viewModelScope
    private val coroutineScope: CoroutineScope = viewModelScope
) : ViewModel() {

    fun loadSensors() {
        coroutineScope.launch(Dispatchers.IO) {
            sensorsService.getUserSensors("", "")
                .execute().let {
                    println("inside getUserSensors()")
                    if (it.isSuccessful) {
                        loadingDialog.postValue(Event(false))
                        consumeLoadedSensors(it)
                    } else {
                        loadingDialog.postValue(Event(false))
                        showLoadSensorsFailure()
                    }
                }
        }
    }
}

修改测试代码

在测试中创建使用TestDispatcher的CoroutineScope,注入到ViewModel:

@Test
fun `Loads sensors`() = runTest {
    val testScope = CoroutineScope(testDispatcher) // runTest自带的测试调度器
    val viewModel = YourViewModel(sensorsService, testScope)
    
    val mDispatcher = object : Dispatcher() {
        override fun dispatch(request: RecordedRequest): MockResponse {
            val sensorsRegex = Regex("/")
            return if (request.path?.matches(sensorsRegex) == true) {
                MockResponse().setBody("[1]")
            } else {
                MockResponse().setResponseCode(500)
            }
        }
    }
    mockWebServer.dispatcher = mDispatcher

    viewModel.loadSensors()
    // 协程在testDispatcher上运行,runTest会自动等待所有协程执行完成
}

方案2:替换Dispatchers.IO为TestDispatcher(适合快速调试)

如果不想修改ViewModel的构造,可以在测试中替换全局的Dispatchers.IO为测试调度器,让ViewModel里的协程在可控环境执行:

修改测试类的初始化/销毁逻辑

@OptIn(ExperimentalCoroutinesApi::class)
@BeforeAll
fun beforeAll() {
    Dispatchers.setMain(mainThreadSurrogate)
    // 把IO调度器替换为测试调度器
    Dispatchers.setIo(testDispatcher)
}

@OptIn(ExperimentalCoroutinesApi::class)
@AfterAll
fun afterAll() {
    Dispatchers.resetMain()
    Dispatchers.resetIo() // 重置IO调度器,避免影响其他测试
    mainThreadSurrogate.close()
}

测试方法无需修改

这样ViewModel里的CoroutineScope(Dispatchers.IO)会自动使用测试调度器,runTest会等待协程执行完毕,execute()代码块就能正常运行了。

额外建议

Retrofit的execute()是同步阻塞方法,在协程里更推荐使用挂起函数(定义Retrofit接口时用suspend fun),既避免阻塞线程,测试也更友好:

// 改造SensorsApi接口为挂起函数
interface SensorsApi {
    @GET("/")
    suspend fun getUserSensors(@Query("param1") p1: String, @Query("param2") p2: String): Response<List<Int>>
}

// ViewModel里调用方式简化
coroutineScope.launch(Dispatchers.IO) {
    val response = sensorsService.getUserSensors("", "")
    // 后续逻辑...
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.11 07:05:29