Hi, I'm pretty new with arrow and trying to migrat...
# arrow
p
Hi, I'm pretty new with arrow and trying to migrate my code to the new arrow.fx.coroutines. I struggled with my tests and have few questions : 1. what is it the right way to test suspend fun ? I do
assertThat(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 ๐Ÿ™‚
s
1. Depends a little bit on what framework you're using. https://github.com/kotest/kotest has support for writing suspendable tests, so you there is no need to use
Enviroment()
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.
p
1. Yes I'm using JUnit5. I've enough things to re-learn with the switch from java to kotlin+arrow ๐Ÿ™‚ Kotest is one of my next step 2. thx, and ๐Ÿ‘ for syntax of Duration 3. I think it's this extra transparent parameter which avoid me to mock the suspending function. Here an example of what i'm trying to do :
Copy code
suspend 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) :
Copy code
@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)
*/
Copy code
@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");
*/
Copy code
@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");
*/
Let me know if i'm wrong with my approach of a progress "listener"
s
Mockk
has support for suspend functions: https://mockk.io/
p
@stojan I'm trying it but it doesn't seems to work
Copy code
@Test
	fun test1() {
		val progressCaptor = mockk<suspend (Int) -> Unit>("progressCaptor")
		coEvery { progressCaptor(any()) } coAnswers { println("progressCaptor caught any") }

		Environment().unsafeRunSync { doStuffWithProgress(progressCaptor) }
	}
returns
Copy code
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 {})
s
Personally I haven't used it ๐Ÿ™ˆ When doing FP the amount of dependencies goes down for me (since more code gets written as pure functions), the remaining ones are usually behind an interface, so I roll my own fakes
โ˜๏ธ 4
p
I agree that purity is the holy Graal and I wrote
doStuffWithProgress
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 ๐Ÿ™‚
s
Typically we don't mock pure functions and effectful functions like
suspend
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.
s
for your example you could pass:
Copy code
var called = false
val fakeProgressCaptor = { _: Int -> called = true; Unit }
pass that to your function, then verify
called ==true
p
'was so simple ... thx to get me down on the ground ๐Ÿ™‚
s
happy to help