I need to Unit-Test a function doClassAction
and inside it calls the function doStoreAction
on the object store
.
This doStoreAction
accepts two parameters, one integer and one function, like in the example below
class MyStore() {
fun doStoreAction(num: Int, callback: (text: String) -> Unit) {
// It does something
// ...
// and when it's done it calls the callback
return callback("some text")
}
}
class MyClass(private val store: MyStore,
private val objectX: MyObject) {
fun doClassAction(num: Int) {
store.doStoreAction(num) { callbackResult ->
// Action (potentially asynchronous)
// that will happen after obtaining the value via the callback
objectX.doObjectAction(callbackResult)
}
}
}
I am using Mockito
and so far my test has the following lines
@Test
fun doClassActionTest() {
val mockStore = mock(MyStore::class.java)
val mockObject = mock(MyObject::class.java)
val class = MyClass(mockStore, mockObject)
class.doClassAction(42)
}
But it doesn't compile because the call to the method does not specify the callback... I don't want to overwrite it since I want the test to use the original one and verify that the things inside are called properly, like:
when(objectX.doObjectAction(anyString())).doNothing()
verify(objectX, times(1)).doObjectAction(callbackResult)
Is there a way to call the method and also mock the callback response to a value I set in the tests but still have it go through the original code flow?
After days of trying out things and reading about it, I finally found a solution that works and I'll share it here for whomever find themselves in the same pickle
@Test
fun doClassActionTest() {
val mockStore = mock(MyStore::class.java)
val mockObject = mock(MyObject::class.java)
val myClass = MyClass(mockStore, mockObject)
val input = 42
val resultCallback = "test result"
`when`(mockStore.doStoreAction(any(), any())).then { invocation ->
(invocation.arguments[1] as (String) -> Unit).invoke(resultCallback)
}
myClass.doClassAction(input)
verify(mockObject, only()).doObjectAction(resultCallback)
}
The caveat here is to remember to put the classes that you are mocking AND the methods called by those classes as open
in Kotlin otherwise there could be false positives
P.s. I also used few inline functions extracted from the mockito-kotlin library such as:
inline fun <reified T : Any> any() = Mockito.any(T::class.java) ?: createInstance<T>()
inline fun <reified T : Any> createInstance(): T = createInstance(T::class)
fun <T : Any> createInstance(kClass: KClass<T>): T = castNull()
@Suppress("UNCHECKED_CAST")
private fun <T> castNull(): T = null as T
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With