单元测试基于CallbackFlow的广播接收器时触发Turbine超时无值返回错误
单元测试基于CallbackFlow的广播接收器时触发Turbine超时无值返回错误
我来帮你排查这个Turbine超时的问题,咱们一步步拆解问题、解决问题:
先揪出代码里的几个致命小错误
这些细节问题直接导致了你的测试无法正常触发接收器逻辑:
- 测试类里的
ontentManager变量名少打了一个c,应该是contentManager(虽然mock的时候写对了,但变量名笔误容易出问题) - 初始化接收器时,
lateinit var consentUpdatedBroadcastReceiverImp: consentUpdatedBroadcastReceiverImp类名首字母小写了,要和实际类名ConsentUpdatedBroadcastReceiverImp保持一致 - 最核心的问题:你实际代码里用
ContextCompat.registerReceiver注册广播,但测试里mock的是context.registerReceiver,这俩完全不是同一个方法!所以测试根本没捕获到实际注册的接收器,调用onReceive自然不会有值流入Turbine - 测试里发送广播用的Action是
OTBroadcastServiceKeys.OT_CONSENT_UPDATED,但实际代码监听的是BroadcastServiceKeys.CONSENT_UPDATED,Action不匹配的话,接收器根本不会响应
修正后的测试代码&关键解释
我把所有问题都修正了,同时优化了协程调度的逻辑,确保测试能正常触发流的发射:
class ConsentUpdatedBroadcastReceiverImpTest { private val contentManager = mock<ContentManager>() private val testDispatcher = StandardTestDispatcher() private val applicationScope = TestScope(testDispatcher) private val context = mock<Context>() private lateinit var consentUpdatedBroadcastReceiverImp: ConsentUpdatedBroadcastReceiverImp @Before fun setUp() { consentUpdatedBroadcastReceiverImp = ConsentUpdatedBroadcastReceiverImp( context = context, contentManager = contentManager, applicationScope = applicationScope ) } @Test fun `given after receiving a broadcast then send broadcast`() = runTest(testDispatcher) { // Given val consentStatusPreference = mock<ConsentStatusPreference> { on { performance } doReturn ConsentStatusType.DENIED on { functionality } doReturn ConsentStatusType.ACCEPTED on { targeting } doReturn ConsentStatusType.NOT_COLLECTED } whenever(contentManager.getConsentStatusPreference()).thenReturn(consentStatusPreference) // 关键:mock ContextCompat的静态注册方法,而不是Context的实例方法 var registeredReceiver: BroadcastReceiver? = null whenever( ContextCompat.registerReceiver( eq(context), any<BroadcastReceiver>(), eq(IntentFilter(BroadcastServiceKeys.CONSENT_UPDATED)), eq(ContextCompat.RECEIVER_NOT_EXPORTED) ) ).thenAnswer { registeredReceiver = it.getArgument(1) null // 模拟注册成功的返回值 } // mock注销方法,避免测试抛出未mock的调用异常 doNothing().`when`(context).unregisterReceiver(any<BroadcastReceiver>()) // When consentUpdatedBroadcastReceiverImp.observe.test { // 发送和接收器监听一致的Action的广播 registeredReceiver?.onReceive(context, Intent(BroadcastServiceKeys.CONSENT_UPDATED)) // 推进测试调度器,确保所有协程任务执行完毕 advanceUntilIdle() // Then val receivedPreference = awaitItem() assert(receivedPreference === consentStatusPreference) cancelAndIgnoreRemainingEvents() } } }
关键修正点说明
- mock正确的注册方法:
实际代码用的是静态方法ContextCompat.registerReceiver,所以必须mock这个方法才能捕获到注册的接收器(需要确保你的测试依赖里加了mockito-inline,不然没法mock静态方法) - Action严格匹配:
发送广播的Intent Action必须和接收器监听的BroadcastServiceKeys.CONSENT_UPDATED完全一致,不然接收器不会响应 - 协程调度同步:
用runTest(testDispatcher)指定测试调度器,同时调用advanceUntilIdle()确保所有协程任务(包括shareIn的流订阅、CallbackFlow的发射)都执行完毕 - 处理注销逻辑:
当CallbackFlow的awaitClose触发时会调用unregisterReceiver,提前mock这个方法避免测试抛出异常
额外小建议
- 如果
ConsentStatusPreference是数据类,建议直接创建实例而不是mock,这样测试更稳定,也减少mock带来的额外复杂度 - 确保你的测试gradle依赖里已经添加了
mockito-inline,不然没法mockContextCompat的静态方法
这样修改后,你的测试应该就能正常捕获到流里的值,不会再出现Turbine超时的问题了。
内容来源于stack exchange
相关产品推荐
相关产品推荐

