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

Kotlin协程结合Retrofit的PriceService带Mock单元测试编写求助

PriceService 单元测试实现方案

前置依赖配置

需要先在测试依赖中引入以下组件:

  • 协程测试库:kotlinx-coroutines-test:1.7.3(版本和项目协程版本保持一致即可)
  • Mock框架:推荐用MockK,依赖为 io.mockk:mockk:1.13.8,也可以替换为Mockito,本文示例用MockK实现
  • Junit基础测试依赖:junit:junit:4.13.2(Junit5对应调整配置即可)

业务类优化说明

原有PriceService硬编码了APIClient初始化逻辑,无法直接Mock APIInterface,先做最小改造,不影响原有线上逻辑:

class PriceService(
    // 构造函数注入APIInterface,默认保持原有初始化逻辑,不影响线上调用
    private val apiInterface: APIInterface? = APIClient.getClient()?.create(APIInterface::class.java)
) {
    fun main(priceCallback: PriceServiceCallBack) {
        val exceptionHandler = CoroutineExceptionHandler { _, throwable ->
            println("Caught $throwable")
            // 可按需补全异常时的回调逻辑
        }
        CoroutineScope(Dispatchers.IO).launch(exceptionHandler) {
            val response = apiInterface?.prices()
            withContext(Dispatchers.Main) {
                if (response!!.isSuccessful) {
                    priceCallback.getData(response.body())
                } else {
                    onError("Error : ${response.message()} ")
                }
            }
        }
    }
    
    // 原有onError方法保持不变
    private fun onError(msg: String) {
        // 原有业务逻辑
    }
}

你提到的Retrofit无法调用enqueue/execute的问题,因为当前代码里prices()是Retrofit声明的suspend挂起方法,直接返回Response对象,所以单元测试里不需要调用这两个方法,直接Mock挂起方法的返回值即可。


测试用例实现

首先实现协程调度器替换的测试规则,避免单元测试找不到Android主线程的问题:

@OptIn(ExperimentalCoroutinesApi::class)
class CoroutineTestRule : TestWatcher() {
    val testDispatcher = StandardTestDispatcher()

    override fun starting(description: Description?) {
        super.starting(description)
        Dispatchers.setMain(testDispatcher)
    }

    override fun finished(description: Description?) {
        super.finished(description)
        Dispatchers.resetMain()
    }
}

正式测试类实现:

@OptIn(ExperimentalCoroutinesApi::class)
class PriceServiceTest {
    // 注入协程测试规则,替换调度器
    @get:Rule
    val coroutineRule = CoroutineTestRule()

    // Mock依赖对象
    private val mockApi = mockk<APIInterface>()
    private val mockCallback = mockk<PriceServiceCallBack>()
    // 测试用PriceService实例,传入Mock的API接口
    private val priceService = PriceService(mockApi)

    @Test
    fun `请求成功时回调正确返回数据`() = runTest {
        // 1. 构造模拟返回数据,替换为你实际的Price数据结构
        val mockPriceData = listOf("100", "200", "300")
        val mockResponse = Response.success(mockPriceData)
        // 2. Mock API接口的prices方法返回值
        coEvery { mockApi.prices() } returns mockResponse
        // 3. Mock回调方法处理
        every { mockCallback.getData(any()) } just Runs

        // 4. 执行测试方法
        priceService.main(mockCallback)
        // 推进协程执行直到所有任务完成
        coroutineRule.testDispatcher.scheduler.advanceUntilIdle()

        // 5. 验证调用符合预期
        coVerify(exactly = 1) { mockApi.prices() }
        verify(exactly = 1) { mockCallback.getData(mockPriceData) }
    }

    @Test
    fun `请求失败时触发错误逻辑`() = runTest {
        // 1. 构造失败响应
        val mockErrorResponse = Response.error<String>(400, "请求参数错误".toResponseBody("text/plain".toMediaType()))
        // 2. Mock接口返回失败
        coEvery { mockApi.prices() } returns mockErrorResponse as Response<*>
        // 如果onError方法会调用callback的错误回调,此处补充Mock逻辑即可

        // 3. 执行测试
        priceService.main(mockCallback)
        coroutineRule.testDispatcher.scheduler.advanceUntilIdle()

        // 4. 验证逻辑
        coVerify(exactly = 1) { mockApi.prices() }
        // 按需补充错误逻辑的验证
    }

    @Test
    fun `接口抛出异常时触发异常捕获`() = runTest {
        // 1. Mock接口调用抛出异常
        coEvery { mockApi.prices() } throws RuntimeException("网络异常")

        // 2. 执行测试
        priceService.main(mockCallback)
        coroutineRule.testDispatcher.scheduler.advanceUntilIdle()

        // 3. 验证异常被正常捕获
        coVerify(exactly = 1) { mockApi.prices() }
    }
}

兼容说明

  • 如果使用旧版本协程测试库,把runTest替换为runBlocking,调度器推进逻辑对应调整即可
  • 如果用Mockito作为Mock框架,把MockK的coEvery/coVerify替换为Mockito的whenever/verify,搭配Mockito-Kotlin实现协程方法Mock即可
  • 原代码中response!!.isSuccessful的非空断言遇到response为null时会崩溃,建议补充空判断优化逻辑

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.06 23:54:01