androidkotlinunit-testingmockkmockk-verify

Mockk unable to verify lambda that takes a data class as an argument


import io.mockk.verify
import org.junit.jupiter.api.Test

class DummyTest {

    @Test
    fun `dummy test to show issue with mockk`() {
        val lambdaFunc: (SomeDataClass) -> Unit = { }
        dummyFunc(lambdaFunc)

        verify(exactly = 1) {
            lambdaFunc(any<SomeDataClass>(any()))     // using lambdaFunc(any<SomeDataClass>(any<AnotherDataClass>())) gives Argument type mismatch error
        }
    }
}

data class AnotherDataClass(val arg1: String, val arg2: String)

sealed class SomeSealedClass {
    data class SomeDataClass(val anotherDataClass: AnotherDataClass): SomeSealedClass()
}

fun dummyFunc(
    onEvent: (SomeDataClass) -> Unit
) {
    onEvent(SomeDataClass(AnotherDataClass("arg1", "arg2")))
}

When I run the above, I get the following error:

Receiver class kotlin.reflect.KClass$Subclass1 does not define or inherit an implementation of the resolved method 'abstract boolean isValue()' of interface kotlin.reflect.KClass.
java.lang.AbstractMethodError: Receiver class kotlin.reflect.KClass$Subclass1 does not define or inherit an implementation of the resolved method 'abstract boolean isValue()' of interface kotlin.reflect.KClass.
    at io.mockk.impl.recording.JvmSignatureValueGenerator.signatureValue(JvmSignatureValueGenerator.kt:19)
    at io.mockk.impl.recording.states.RecordingState.matcher(RecordingState.kt:46)
    at io.mockk.impl.recording.CommonCallRecorder.matcher(CommonCallRecorder.kt:52)
    at io.mockk.MockKMatcherScope.match(API.kt:695)
    at io.mockk.MockKMatcherScope.any(API.kt:745)
    at com.sarim.puzzle.data.usecase.DummyTest.dummy_test_to_show_issue_with_mockk$lambda$0(DummyTest.kt:16)
    at io.mockk.impl.eval.RecordedBlockEvaluator$record$block$1.invoke(RecordedBlockEvaluator.kt:24)
    at io.mockk.impl.eval.RecordedBlockEvaluator$enhanceWithRethrow$1.invoke(RecordedBlockEvaluator.kt:76)
    at io.mockk.impl.recording.JvmAutoHinter.autoHint(JvmAutoHinter.kt:23)
    at io.mockk.impl.eval.RecordedBlockEvaluator.record(RecordedBlockEvaluator.kt:39)
    at io.mockk.impl.eval.VerifyBlockEvaluator.verify(VerifyBlockEvaluator.kt:30)
    at io.mockk.MockKDsl.internalVerify(API.kt:120)
    at io.mockk.MockKKt.verify(MockK.kt:218)
    at io.mockk.MockKKt.verify$default(MockK.kt:209)
    at com.sarim.puzzle.data.usecase.DummyTest.dummy test to show issue with mockk(DummyTest.kt:15)
    at java.base/java.lang.reflect.Method.invoke(Unknown Source)
    at java.base/java.util.ArrayList.forEach(Unknown Source)
    at java.base/java.util.ArrayList.forEach(Unknown Source)

Most solutions I have seen to this do not deal with nested Data classes. I am not even sure if Mockk supports this kind of verification.

The culprit is here:

verify(exactly = 1) { lambdaFunc(any<SomeDataClass>(any())) // using lambdaFunc(any<SomeDataClass>(any<AnotherDataClass>())) gives Argument type mismatch error }

But I am not sure how to change this line to verify behavior


Solution

  • Okay, I have finally found a way to make this work. I am re-posting the modified version of my code again

    import com.sarim.puzzle.data.usecase.SomeSealedClass.SomeDataClass
    import com.sarim.puzzle.data.usecase.SomeSealedClass.SomeOtherDataClass
    import io.mockk.mockk
    import io.mockk.verify
    import org.junit.jupiter.api.Test
    
    class DummyTest {
    
        @Test
        fun `dummy test to show issue with mockk`() {
            val lambdaFunc1: (SomeSealedClass) -> Unit = mockk(relaxed = true)
            val lambdaFunc2: (SomeSealedClass) -> Unit = mockk(relaxed = true)
            dummyFunc(lambdaFunc1, lambdaFunc2)
    
            verify(exactly = 1) {
                lambdaFunc1(
                    withArg {
                        it is SomeOtherDataClass && it.arg == "arg"
                    }
                )
            }
    
            verify(exactly = 1) {
                lambdaFunc2(
                    withArg {
                        it is SomeDataClass && it.anotherDataClass.arg1 == "arg1" && it.anotherDataClass.arg2 == "arg2"
                    }
                )
            }
        }
    }
    
    data class AnotherDataClass(val arg1: String, val arg2: String)
    
    sealed class SomeSealedClass {
        data class SomeDataClass(val anotherDataClass: AnotherDataClass): SomeSealedClass()
        data class SomeOtherDataClass(val arg: String): SomeSealedClass()
    }
    
    fun dummyFunc(
        onEvent1: (SomeSealedClass) -> Unit,
        onEvent2: (SomeSealedClass) -> Unit
    ) {
        onEvent1(SomeOtherDataClass("arg"))
        onEvent2(SomeDataClass(AnotherDataClass("arg1", "arg2")))
    }
    

    Basically, you need to mock the lambdas and then use withArg to first verify the type of Data Class that you are passing in and, if that data class has further arguments, you can verify those too.