Padawanchichi
06/27/2019, 3:38 PMsuspend fun <T> awaitCallback (block: (Callback<T>) -> Unit) : T =
suspendCancellableCoroutine { continuation ->
block(object : Callback<T> {
override fun onComplete(result: T) {
if (continuation.isActive) {
continuation.resume(result)
} else {
...
}
}
})
}
I admit I’m not fully mastering that snippet (could be arguable to use it in our codebase) but it’s allowing us to get rid of callbacks when there’s no alternatives. We’re using it with firebase iirc.
It’s working well, however we have a lot of trouble covering that particular snippet of code using Unit Tests. We’re using Mockito/Powermockito and seting up the unit test with this code :
@Captor
private lateinit var callbackCaptor: ArgumentCaptor<Callback<Any>>
@Mock
private lateinit var lambdaReceiver: (Callback<Any>) -> Unit
Anybody do have tips to cover that the above method using Mockito/Powermockito ?Eric Martori
06/27/2019, 4:17 PMverify(lambdaReceiver.invoke(callbackCaptor))
after this line you have the callback in callbackCaptor
You can call the callback with diferent values at this point.
The problem is verify the return of you method after the invocation of the callback.
You can launch a coroutine and the the first call in it and the verification after it. It should look something like this:
launch {
val result = awaitCallback(lambdaReceiver)
//do your assertions for example
assertEquals(expected, result)
}
verify(lambdaReceiver.invoke(callbackCaptor))
callbackCaptor.value.oncomplete(expected)
Eric Martori
06/27/2019, 4:21 PM.join()
after the oncomplete
is calledPadawanchichi
06/27/2019, 4:21 PMEric Martori
06/27/2019, 4:22 PMrunBlocking
so the java process waits for the completion of the coroutinePadawanchichi
06/27/2019, 4:24 PMPadawanchichi
06/27/2019, 4:28 PMPadawanchichi
06/27/2019, 4:30 PMgiven(lambdaReceiver.invoke(any())).willAnswer {
val argument = it.getArgument<(Callback<Any>) -> Unit>(0)
val completion = argument as (Callback<Any>) -> Unit
completion.invoke(callbackCaptor.capture())
}
Eric Martori
06/27/2019, 4:47 PMverify
before the code inside the launch
is calledPadawanchichi
06/27/2019, 4:50 PMEric Martori
06/27/2019, 4:57 PMlaunch {
val result = awaitCallback(lambdaReceiver)
//do your assertions for example
assertEquals(expected, result)
}
launch {
verify(lambdaReceiver.invoke(callbackCaptor))
callbackCaptor.value.oncomplete(expected)
}
Padawanchichi
06/27/2019, 5:01 PMPadawanchichi
06/27/2019, 5:01 PMEric Martori
06/27/2019, 5:02 PMawaitCallback
should run in diferent coroutine than the rest so it can suspend while it waits for the function to return.
With my previous example it didn't work because it was calling verify before any code in the launch
was called, wrapping the verify
and `onComplete`in another launch
allows the suspend function to be called and suspendEric Martori
06/27/2019, 5:03 PMGlobalScope.launch
it is possible that the code is never actually runPadawanchichi
06/27/2019, 5:04 PMPadawanchichi
06/27/2019, 5:04 PMEric Martori
06/27/2019, 5:04 PMPadawanchichi
06/27/2019, 5:08 PMEric Martori
06/27/2019, 5:19 PMPadawanchichi
06/27/2019, 5:20 PMPadawanchichi
06/27/2019, 5:21 PMPadawanchichi
06/27/2019, 5:21 PMEric Martori
06/27/2019, 6:27 PM@Test
fun testAwaitCallback() {
val mockedBlock: (Callback<Int>) -> Unit = mockk(relaxed = true)
val slot = slot<Callback<Int>>()
runBlocking {
launch {
val result = awaitCallback(mockedBlock)
assertEquals(5, result)
}
launch {
verify {
mockedBlock.invoke(capture(slot))
}
slot.captured.onComplete(5)
}
}
}
Eric Martori
06/27/2019, 6:27 PMfail
in either block it fails the testPadawanchichi
06/27/2019, 6:30 PMPadawanchichi
06/28/2019, 1:20 PMPadawanchichi
06/28/2019, 1:20 PM@Test
fun awaitCallback() {
runBlocking {
launch {
val result = sut.awaitCallback(lambdaReceiver)
assertThat(result).isEqualTo(callbackValue)
}
launch {
then(lambdaReceiver).should().invoke(capture(callbackCaptor))
callbackCaptor.value.onComplete(callbackValue)
}
}
}
Padawanchichi
06/28/2019, 1:21 PMPadawanchichi
06/28/2019, 1:23 PMEric Martori
06/28/2019, 1:45 PMPadawanchichi
06/28/2019, 2:05 PMPadawanchichi
06/28/2019, 2:07 PMPadawanchichi
06/28/2019, 2:08 PMPadawanchichi
06/28/2019, 2:09 PM