如何对带回调的Kotlin suspend挂起函数进行单元测试
问题原因
你遇到的报错是因为initialize方法内的suspendCoroutine始终未被恢复:你仅mock了Sdk实例,但没有触发sdk.initialize传入的callback/onFailure回调,协程一直处于挂起状态,runBlockingTest等待任务完成超时就抛出了该异常。
测试实现方案
我们可以通过Mockito的参数捕获器(ArgumentCaptor)拿到sdk.initialize传入的两个回调参数,手动触发回调来模拟成功、失败两种场景,同时也可以覆盖sdk方法直接崩溃的场景。
以下是三种场景的测试代码,如果你用的是旧版协程测试库,把代码中的runTest替换为runBlockingTest即可:
1. 初始化成功场景测试
@Test fun `initialize should resume successfully when sdk callback is triggered`() = runTest { val sdk = mock<Sdk>() // 定义参数捕获器,捕获sdk.initialize传入的两个回调 val callbackCaptor = argumentCaptor<() -> Unit>() val failureCaptor = argumentCaptor<(Throwable) -> Unit>() // 启动initialize方法,此时协程挂起 val initializeJob = async { initialize(sdk) } // 验证sdk.initialize被调用,同时拿到两个回调参数 verify(sdk).initialize(callbackCaptor.capture(), failureCaptor.capture()) // 手动触发成功回调,恢复协程 callbackCaptor.firstValue.invoke() // 等待协程执行完成,无异常抛出即为测试通过 initializeJob.await() }
2. 初始化失败回调触发场景测试
@Test fun `initialize should throw SdkException when onFailure callback is triggered`() = runTest { val sdk = mock<Sdk>() val callbackCaptor = argumentCaptor<() -> Unit>() val failureCaptor = argumentCaptor<(Throwable) -> Unit>() val testError = RuntimeException("Sdk init failed") val initializeJob = async { initialize(sdk) } verify(sdk).initialize(callbackCaptor.capture(), failureCaptor.capture()) // 手动触发失败回调 failureCaptor.firstValue.invoke(testError) // 验证抛出的异常符合预期 val exception = shouldThrow<SdkException> { initializeJob.await() } assert(exception.message == testError.message) }
3. Sdk.initialize直接抛出异常场景测试
@Test fun `initialize should throw SdkException when sdk initialize crashes directly`() = runTest { val sdk = mock<Sdk>() val testCrash = RuntimeException("Inner sdk crash") // mock sdk.initialize被调用时直接抛出异常 whenever(sdk.initialize(any(), any())).thenThrow(testCrash) // 验证抛出的异常符合预期 val exception = shouldThrow<SdkException> { initialize(sdk) } assert(exception.message == "Crash inside SDK") assert(exception.cause == testCrash) }
内容的提问来源于stack exchange,提问作者Henrique Rocha
相关产品推荐
相关产品推荐

