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