Nino
06/21/2022, 12:02 PMonStart
and delay
but it's smelly.
Source code:
// I don't control the ConnectivityManager, it's from Android
class ConnectivityRepository(private val connectivityManager: ConnectivityManager) {
fun isInternetAvailableFlow(): Flow<Boolean> = callbackFlow {
val networkCallback = object : ConnectivityManager.NetworkCallback() {
override fun onAvailable(network: Network) {
trySend(true)
}
override fun onLost(network: Network) {
trySend(false)
}
}
connectivityManager.registerDefaultNetworkCallback(networkCallback)
awaitClose { connectivityManager.unregisterNetworkCallback(networkCallback) }
}
}
Unit test:
@Test
fun `happy path`() = runTest {
// Given
val connectivityRepository = ConnectivityRepository(connectivityManager)
val networkCallbackSlot = slot<ConnectivityManager.NetworkCallback>() // Mocking stuff : I can 'capture' something during the test execution with this
val connectivityManager = mockk<ConnectivityManager>() // Mocking the ConnectivityManager
justRun { connectivityManager.registerDefaultNetworkCallback(capture(networkCallbackSlot)) } // This function is mocked and "wired" with the slot
justRun { connectivityManager.unregisterNetworkCallback(any<ConnectivityManager.NetworkCallback>()) }
// When
val result = connectivityRepositoryImpl.isInternetAvailableFlow().onStart { // Ugly solution but nothing else works...
launch {
delay(1)
networkCallbackSlot.captured.onAvailable(mockk())
}
}.first()
// Then
assertThat(result).isTrue()
}
I need to capture the anonymous class extending ConnectivityManager.NetworkCallback
in order to call it with either onAvaible
or onLost
during my test, but at the same time, since this is a cold flow, it won't run until I collect it. But it won't emit something until I run onAvaible
or onLost
. onStart
is too early, and collect
is never called. I'd need a "afterStart" callback on my Flow or something like that ?Michal Klimczak
06/21/2022, 1:28 PMconnectivityRepositoryImpl.isInternetAvailableFlow().test {
networkCallbackSlot.captured.onAvailable(mockk())
awaitItem() shouldBe true
}
ephemient
06/21/2022, 2:01 PMproduce(Dispatchers.UNCONFINED) {
connectivityRepository.isInternetAvailableFlow().collect { send(it) }
}.consume {
verify { connectivityManager.registerDefaultNetworkCallback(capture(networkCallbackSlot)) }
networkCallbackSlot.captured.onAvailable(mockk())
receive() shouldBeEqualTo true
}
Nino
06/21/2022, 2:58 PMMichal Klimczak
06/21/2022, 2:59 PMNino
06/21/2022, 3:06 PMNino
06/21/2022, 3:20 PMephemient
06/21/2022, 3:22 PMrunCurrent()
at the beginning of the consume {}
block. it's to ensure that the collect {}
in the produce {}
block has run far enough that the collector has actually started before you try to make use of things that depend on it starting. if you're only doing tests on receive()
then you don't need it since that'll suspend until there's a value.ephemient
06/21/2022, 3:23 PM.test {}
block