PHaroZ
09/15/2020, 12:57 PMassertThat(Environment().unsafeRunSync { mySuspendFunToTest(arg1,arg2) })...
2. how to delay the response of a stub method (a database query) ? I've tried kotlinx.coroutines.DelayKt#delay
but it seems a bit strange to use kotlinx.corroutine just for that
3. how to mock a suspend fun ? I've tried Environment().unsafeRunSync {whenever(aMock.aSuspendedFun()) doReturn aResult}
(with the help of com.nhaarman.mockitokotlin2) but it doesn't seem to work
thx for your help & let me know if I miss up some documentation ๐simon.vergauwen
09/15/2020, 2:17 PMEnviroment()
or kotlinx.coroutines.runBlocking
to go from the suspend world to the non-suspend world. In case you're using JUnit
I would recommened Enviroment().unsafeRunSync
.
2. Arrow Fx has a sleep
function similar to KotlinX's delay.
3. I have no experience mocking suspending functions ๐ What is not working there? Suspending functions are like regular functions except they have a transparent third Continuation<Any?>
parameter, so I'd expect mocking to work there out of the box.PHaroZ
09/16/2020, 6:09 AMsuspend fun doStuffWithProgress(progressCaptor: suspend (Int) -> Unit) {
val progressFiber = ForkConnected {
for (i in 0..Int.MAX_VALUE) {
sleep(100.milliseconds)
println("inside the loop $i")
progressCaptor(i)
}
}
/* do some long stuff here */ sleep(250.milliseconds)
progressFiber.cancel()
}
and some tries to test that my progressCaptor
suspend fun is called (I'm using com.nhaarman.mockitokotlin2) :
@Test
fun test2() {
val progressCaptor = mock<suspend (Int) -> Unit> {
onBlocking { invoke(0) }.then { println("progressCaptor caught #0") }
onBlocking { invoke(1) }.then { println("progressCaptor caught #1") }
}
Environment().unsafeRunSync { doStuffWithProgress(progressCaptor) }
}
/* which prints :
inside the loop 0
inside the loop 1
org.mockito.exceptions.misusing.UnnecessaryStubbingException:
Unnecessary stubbings detected.
Clean & maintainable test code requires zero unnecessary code.
Following stubbings are unnecessary (click to navigate to relevant line of code):
1. -> at com.biron.linkum.utilsTest.Test$test2$progressCaptor$1$1.invokeSuspend(ATestTest.kt:45)
2. -> at com.biron.linkum.utilsTest.Test$test2$progressCaptor$1$3.invokeSuspend(ATestTest.kt:46)
*/
@Test
fun test3() {
val progressCaptor = mock<suspend (Int) -> Unit> {
onBlocking { invoke(any()) }.then { println("progressCaptor caught any") }
}
Environment().unsafeRunSync { doStuffWithProgress(progressCaptor) }
}
/* which prints :
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at com.biron.linkum.utilsTest.Test$test3$progressCaptor$1$1.invokeSuspend(ATestTest.kt:72)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
*/
@Test
fun test4() {
Environment().unsafeRunSync {
val progressCaptor = mock<suspend (Int) -> Unit>()
whenever(progressCaptor(any())).then { println("progressCaptor caught any") }
doStuffWithProgress(progressCaptor)
}
}
/* which prints :
org.mockito.exceptions.misusing.InvalidUseOfMatchersException:
Invalid use of argument matchers!
2 matchers expected, 1 recorded:
-> at com.biron.linkum.utilsTest.Test$test4$1.invokeSuspend(ATestTest.kt:100)
This exception may occur if matchers are combined with raw values:
//incorrect:
someMethod(anyObject(), "raw String");
*/
PHaroZ
09/16/2020, 6:15 AMstojan
09/16/2020, 7:13 AMMockk
has support for suspend functions: https://mockk.io/PHaroZ
09/16/2020, 7:17 AM@Test
fun test1() {
val progressCaptor = mockk<suspend (Int) -> Unit>("progressCaptor")
coEvery { progressCaptor(any()) } coAnswers { println("progressCaptor caught any") }
Environment().unsafeRunSync { doStuffWithProgress(progressCaptor) }
}
returns
Creating mockk for Function2 name=progressCaptor#1
Creating mockk for Any name=child of progressCaptor#1#2
inside the loop 0
Throwing io.mockk.MockKException: no answer found for: Function2(progressCaptor#1).invoke(0, continuation {}) on Function2(progressCaptor#1).invoke(0, continuation {})
stojan
09/16/2020, 7:20 AMPHaroZ
09/16/2020, 7:34 AMdoStuffWithProgress
as pure as possible by using arrow IO at first and now arrow-fx-coroutines. If i'm on the wrong path with my function can you tell me how I can write it to made it more pure & more testable ?
A world without end-users would be easier, and I could do without a progress meter ๐simon.vergauwen
09/16/2020, 7:38 AMsuspend
are typically encapsulated in an interface
. Those functions can then be easily be "mocked" by providing a fake implementation. Which typically is much easier than using/learning a mocking framework, since you end up with all vanilla Kotlin code.stojan
09/16/2020, 9:56 AMvar called = false
val fakeProgressCaptor = { _: Int -> called = true; Unit }
pass that to your function, then verify called ==true
PHaroZ
09/16/2020, 10:02 AMstojan
09/16/2020, 11:05 AM