36

I create a mock of a class with mockk. On this mock I now call a method that gets a lambda as a parameter.

This lambda serves as a callback to deliver state changes of the callback to the caller of the method.

class ObjectToMock() {
    fun methodToCall(someValue: String?, observer: (State) -> Unit) {
        ...
    }
}

How do I configure the mock to call the passed lambda?

Janusz
  • 187,060
  • 113
  • 301
  • 369

3 Answers3

64

You can use answers:

val otm: ObjectToMock = mockk()
every {  otm.methodToCall(any(), any())} answers {
    secondArg<(String) -> Unit>().invoke("anything")
}

otm.methodToCall("bla"){
    println("invoked with $it") //invoked with anything
}

Within the answers scope you can access firstArg, secondArg etc and get it in the expected type by providing it as a generic argument. Note that I explicitly used invoke here to make it more readable, it may also be omitted.

s1m0nw1
  • 76,759
  • 17
  • 167
  • 196
  • 3
    NB: This works like a charm for Kotlin lambas, if you are trying to mock an interface passed with the syntax of a lamba it will result in a `ClassCastException [...] cannot be cast to kotlin.jvm.functions.Function1`. In that case you shuld do something like `secondArg().onDummyEvent("dummy-value")`. – ilbose Sep 22 '21 at 13:36
4

Maybe not exactly what you ask about, but you can use the funciton type for the mock:

val observerMock = mockk<(State) -> Unit>()
Marius K
  • 498
  • 5
  • 6
  • Exactly what I needed, thanks! An additional tip would be to relax the mock, and then use verify to ensure it was called – E.T. Apr 14 '23 at 05:56
3

I had to look for a bit more example for the callback and found some example in Kotlin Test with Mockk. In my case, it's a bit more specific. I wanted to check and mock the onFailure and onSuccess case of a a custom callback implementation MyCustomCallback implementing the ListenableFutureCallback.

The code would look like that for my ExampleProducer class that would have a send function:

fun send(data: String) {
    val responseFuture = kafkaTemplate.send(topic, data)
    responseFuture.addCallback(MyCustomCallback())
}

So here who would the test go:

@Test
fun onFailureTest() {
    kafkaTemplate: KafkaTemplate<String, String> = mockk()
    val captureCallback = slot<ListenableFutureCallback<SendResult<String, String>>>()
    
    every { callback.addCallback(capture(captureCallback)) } answers {
               captureCallback.captured.onFailure(Throwable())
    }
    every { kafkaTemplate.send(any()) } returns callback
    
    val prod: ExampleProducer = ExampleProducer()
    prod.send("test")
    
    // Then you can verify behaviour or check your captureCallback.captured
    verify { kafkaTemplate.send(any()) }
    assertNotNull(captureCallback.captured)
}
Sylhare
  • 5,907
  • 8
  • 64
  • 80