-1

I have the following class I am trying to test. I am using mockito as that is the library I am using

class ConsentUpdatedBroadcastReceiverImp @Inject constructor(
    private val context: Context,
    private val contentManager: ContentManager,
    private val applicationScope: CoroutineScope,
) : ConsentUpdatedBroadcastReceiver {
    override val observe: Flow<ConsentStatusPreference>
        get() {
            return callbackFlow {
                val receiver = object : BroadcastReceiver() {
                    override fun onReceive(context: Context?, intent: Intent?) {
                        val consentStatusPreference = contentManager.getConsentStatusPreference()

                        trySend(consentStatusPreference).isSuccess
                    }
                }

                ContextCompat.registerReceiver(
                    context,
                    receiver,
                    IntentFilter(BroadcastServiceKeys.CONSENT_UPDATED),
                    ContextCompat.RECEIVER_NOT_EXPORTED,
                )

                awaitClose {
                    context.unregisterReceiver(receiver)
                }
            }.shareIn(
                scope = applicationScope,
                started = SharingStarted.WhileSubscribed(stopTimeoutMillis = 5_000L),
            )
        }
}

This is my unit test

class ConsentUpdatedBroadcastReceiverImpTest {

    private val ontentManager = 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 {
            // 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)

            var registeredReceiver: BroadcastReceiver? = null
            whenever(context.registerReceiver(any(), any(), any(), anyOrNull())).thenAnswer {
                registeredReceiver = it.getArgument(1)
                null
            }

            // When
            consentUpdatedBroadcastReceiverImp.observe.test {
                registeredReceiver?.onReceive(context, Intent(OTBroadcastServiceKeys.OT_CONSENT_UPDATED))
                val _consentStatusPreference = this.awaitItem()

                // Then
                assert(_consentStatusPreference === consentStatusPreference)
                cancelAndIgnoreRemainingEvents()
            }
        }
    }
}

This is the error when running the test

No value produced in 3s
app.cash.turbine.TurbineAssertionError: No value produced in 3s
    at app//app.cash.turbine.ChannelKt.awaitEvent(channel.kt:89)
    at app//app.cash.turbine.ChannelKt$awaitEvent$1.invokeSuspend(channel.kt)
    at app//kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
    at app//kotlinx.coroutines.DispatchedTask.run(DispatchedTask.kt:100)
    at app//kotlinx.coroutines.test.TestDispatcher.processEvent$kotlinx_coroutines_test(TestDispatcher.kt:24)
    at app//kotlinx.coroutines.test.TestCoroutineScheduler.tryRunNextTaskUnless$kotlinx_coroutines_test(TestCoroutineScheduler.kt:99)
    at app//kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt$runTest$2$1$workRunner$1.invokeSuspend(TestBuilders.kt:322)
    at app//kotlin.coroutines.jvm.internal.BaseContinuationImpl.resumeWith(ContinuationImpl.kt:33)
2
  • 2
    Please replace your code with a minimal reproducible example that we can execute. Commented Jun 20 at 11:28
  • 1
    One robust solution is to use a tool like Mockk static mocking or Mockito’s mockStatic to mock the ContextCompat.registerReceiver call directly. Or Refactor to inject registerReceiver for testability Commented Jun 23 at 11:16

2 Answers 2

1

try this:

class ConsentUpdatedBroadcastReceiverImpTest {

  private val contentManager = mock<ContentManager>()
  private val context = mock<Context>()
  private lateinit var sut: ConsentUpdatedBroadcastReceiverImp

  @Test
  fun `when broadcast arrives then flow emits`() = runTest {
    // 1) Use this TestScope as the applicationScope!
    val applicationScope = this

    // 2) Stub getConsentStatusPreference()
    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)

    // 3) Stub the FIVE-arg overload of context.registerReceiver
    var registeredReceiver: BroadcastReceiver? = null
    whenever(
      context.registerReceiver(
        any<BroadcastReceiver>(),
        any<IntentFilter>(),
        any<String?>(),
        any<Handler?>(),
        anyInt()
      )
    ).thenAnswer { invocation ->
      registeredReceiver = invocation.getArgument(0)
      // our code doesn’t care about the return value
      null
    }

    // 4) Construct SUT with the TEST scope
    sut = ConsentUpdatedBroadcastReceiverImp(
      context = context,
      contentManager = contentManager,
      applicationScope = applicationScope
    )

    // 5) Collect with Turbine
    sut.observe.test {
      // Let shareIn’s subscription coroutine actually start
      testScheduler.runCurrent()

      // Fire the broadcast
      registeredReceiver?.onReceive(
        context,
        Intent(BroadcastServiceKeys.CONSENT_UPDATED)
      )

      // Flush the trySend + shareIn hand-off
      testScheduler.runCurrent()

      // Now we should get our item
      assert(awaitItem() === consentStatusPreference)

      cancelAndIgnoreRemainingEvents()
    }
  }
}
Sign up to request clarification or add additional context in comments.

Comments

1

Looking at your test, there are some issues causing the timeout.

class ConsentUpdatedBroadcastReceiverImpTest {

    @Mock
    private lateinit var contentManager: ContentManager // Fixed typo from ontentManager
    
    @Mock
    private lateinit var context: Context
    
    private val testDispatcher = StandardTestDispatcher()
    private val applicationScope = TestScope(testDispatcher)
    private lateinit var consentUpdatedBroadcastReceiverImp: ConsentUpdatedBroadcastReceiverImp

    @Before
    fun setUp() {
        MockitoAnnotations.openMocks(this)
        
        // Mock static ContextCompat methods
        mockkStatic(ContextCompat::class)
        
        consentUpdatedBroadcastReceiverImp = ConsentUpdatedBroadcastReceiverImp(
            context = context,
            contentManager = contentManager,
            applicationScope = applicationScope
        )
    }

    @After
    fun tearDown() {
        unmockkStatic(ContextCompat::class)
    }

    @Test
    fun `given after receiving a broadcast then send broadcast`() = runTest {
        // 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)

        var registeredReceiver: BroadcastReceiver? = null
        
        // Mock ContextCompat.registerReceiver to capture the receiver
        every { 
            ContextCompat.registerReceiver(any(), any(), any(), any())
        } answers {
            registeredReceiver = secondArg<BroadcastReceiver>()
            null
        }
        
        // Mock unregisterReceiver
        every { context.unregisterReceiver(any()) } just Runs

        // When
        consentUpdatedBroadcastReceiverImp.observe.test {
            // Let the flow set up
            advanceUntilIdle()
            
            // Simulate broadcast
            registeredReceiver?.onReceive(context, Intent(BroadcastServiceKeys.CONSENT_UPDATED))
            
            // Process the emission
            advanceUntilIdle()
            
            // Then
            val receivedPreference = awaitItem()
            assertEquals(consentStatusPreference, receivedPreference)
            
            cancelAndIgnoreRemainingEvents()
        }
    }
}

Key fixes:

  1. Fixed typo: ontentManagercontentManager

  2. Added MockK for static method mocking (ContextCompat.registerReceiver)

  3. Used advanceUntilIdle() to process coroutines

  4. Fixed intent action name to match your class

Add this dependency:

testImplementation "io.mockk:mockk:1.13.8"

The main issue was that ContextCompat.registerReceiver is a static method that needs special mocking, and you need to advance the test dispatcher to let the coroutines execute.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.