Mockito ArgumentCaptor for Kotlin function
Consider a function that takes an interface implementation as an argument like this:
interface Callback {
fun done()
}
class SomeClass {
fun doSomeThing(callback: Callback) {
// do something
callback.done()
}
}
When I want to test the caller of this function, I can do something like
val captor = ArgumentCaptor.forClass(Callback::class)
Mockito.verify(someClass).doSomeThing(captor.capture())
To test what the other class does when the callback is invoked, I can then do
captor.value.done()
Question: How can I do the same if I replace the callback interface with a high order function like
fun doSomeThing(done: () -> Unit) {
// do something
done.invoke()
}
Can this be done with ArgumentCaptor and what class do I have to use in ArgumentCaptor.forClass(???)
Solution 1:
I recommend nhaarman/mockito-kotlin: Using Mockito with Kotlin
It solves this through an inline function with a reified type parameter:
inline fun <reified T : Any> argumentCaptor() = ArgumentCaptor.forClass(T::class.java)
Source: mockito-kotlin/ArgumentCaptor.kt at a6f860461233ba92c7730dd42b0faf9ba2ce9281 · nhaarman/mockito-kotlin
e.g.:
val captor = argumentCaptor<() -> Unit>()
verify(someClass).doSomeThing(captor.capture())
or
val captor: () -> Unit = argumentCaptor()
verify(someClass).doSomeThing(captor.capture())
Solution 2:
I tried what @mfulton26 suggested, but was getting an error message saying captor.capture() must not be null
. and this was what worked for me.
Declared a member variable captor
with @Captor
annotation,
@Captor private lateinit var captor: ArgumentCaptor<Callback>
and in my @Test,
verify(someClass).doSomething(capture(captor))
Solution 3:
Based on mfulton26
's answer, i create an example below.
to show how to invoke the captured function or lambda expression.
you need the mockito-kotlin
Assume we have a Class A, it has a suspend function with two higher order function as parameters.
how can we mock the onSuccess
scenario and onError
scenario
class A {
suspend fun methodB(onSuccess: (ModelA) -> Unit, onError: (ErrorA) -> Unit)
}
Here is the dummy example
// in the unit test class
private val mockClassA = // use annotation or mock()
// decalre the higer oder function capture variables.
private val onSuccessCapture = argumentCaptor<(ModelA) -> Unit>()
private val onErrorCapture = argumentCaptor<(ErrorA) -> Unit>()
@Test
fun testMethodB = testDispatcher.runBlockingTest {
doAnswer {
// on success scenario
val modelA = // get ModelA
onSuccessCapture.firstValue.invoke(modelA) // this line will let the onSuccess parameter been called
// on error scenario
// val errorA = // get ErrorA
//onErrorCapture.firstValue.invoke(errorA)
}.`when`(mockClassA).methodB(onSuccessCapture.capture(), onErrorCapture.capture())
}
Solution 4:
I had this problem just now and solved it with an inline argumentCaptor
from mockito-kotlin
:
argumentCaptor<String>().apply {
verify(myClass, times(2)).setItems(capture())
assertEquals(2, allValues.size)
assertEquals("test", firstValue)
}
firstValue
is a reference to the first captured object.
Source: https://github.com/mockito/mockito-kotlin/wiki/Mocking-and-verifying#argument-captors