lupuuss / Mokkery

The mocking library for Kotlin Multiplatform, easy to use, boilerplate-free and compiler plugin driven.
https://mokkery.dev
Apache License 2.0
202 stars 8 forks source link

iOS, verifying function with prefilled parameter throws `UnsafeValue cannot be cast to class` #44

Closed casvanluijtelaar closed 1 month ago

casvanluijtelaar commented 1 month ago

When we verify a function with a default prefilled parameter that returns a complex (not a primitive) the following error is thrown.

Invalid connection: com.apple.coresymbolicationd

kotlin.ClassCastException: class dev.mokkery.internal.answering.autofill.UnsafeValue cannot be cast to class com.x.presentation.marketpicker.TestAnswer
kotlin.ClassCastException: class dev.mokkery.internal.answering.autofill.UnsafeValue cannot be cast to class com.x.presentation.marketpicker.TestAnswer
    at kotlin.Throwable#<init>(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Throwable.kt:28)
    at kotlin.Exception#<init>(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:23)
    at kotlin.RuntimeException#<init>(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:34)
    at kotlin.ClassCastException#<init>(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/Exceptions.kt:108)
    at <global>.ThrowClassCastException(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/RuntimeUtils.kt:38)
    at com.x.presentation.marketpicker.$prefilledParamThrowsKotlinClassCastException$lambda$2$lambda$1COROUTINE$0.invokeSuspend#internal(/Users/casvanluijtelaar/Projects/x-gmalite-multiplatform/x-marketpicker-module/src/commonTest/kotlin/com/x/presentation/marketpicker/MarketPickerViewModelTests.kt:542)
    at com.x.presentation.marketpicker.prefilledParamThrowsKotlinClassCastException$lambda$2$lambda$1#internal(/Users/casvanluijtelaar/Projects/x-multiplatform/x/src/commonTest/kotlin/com/x/presentation/marketpicker/MarketPickerViewModelTests.kt:542)
    at com.x.presentation.marketpicker.$prefilledParamThrowsKotlinClassCastException$lambda$2$lambda$1$FUNCTION_REFERENCE$104.invoke#internal(Unknown Source)
    at kotlin.coroutines.SuspendFunction1#invoke#suspend(/Users/casvanluijtelaar/.gradle/daemon/8.7/[K][Suspend]Functions:1)
    at dev.mokkery.internal.internalVerifySuspend$lambda$1$lambda$0#internal(/Users/damianbaczynski/IdeaProjects/Mokkery/mokkery-runtime/src/commonMain/kotlin/dev/mokkery/internal/Verify.kt:27)
    at dev.mokkery.internal.$internalVerifySuspend$lambda$1$lambda$0$FUNCTION_REFERENCE$2.invoke#internal(/Users/damianbaczynski/IdeaProjects/Mokkery/mokkery-runtime/src/commonMain/kotlin/dev/mokkery/internal/Verify.kt:27)
    at kotlin.Function1#invoke(/Users/casvanluijtelaar/.gradle/daemon/8.7/[K][Suspend]Functions:1)
    at dev.mokkery.internal.coroutines#runSuspension(/Users/damianbaczynski/IdeaProjects/Mokkery/mokkery-runtime/src/commonMain/kotlin/dev/mokkery/internal/coroutines/RunSuspension.kt:10)
    at dev.mokkery.internal.internalVerifySuspend$lambda$1#internal(/Users/damianbaczynski/IdeaProjects/Mokkery/mokkery-runtime/src/commonMain/kotlin/dev/mokkery/internal/Verify.kt:27)
    at dev.mokkery.internal.$internalVerifySuspend$lambda$1$FUNCTION_REFERENCE$0.invoke#internal(/Users/damianbaczynski/IdeaProjects/Mokkery/mokkery-runtime/src/commonMain/kotlin/dev/mokkery/internal/Verify.kt:27)
    at dev.mokkery.internal.$internalVerifySuspend$lambda$1$FUNCTION_REFERENCE$0.$<bridge-DNNN>invoke(/Users/damianbaczynski/IdeaProjects/Mokkery/mokkery-runtime/src/commonMain/kotlin/dev/mokkery/internal/Verify.kt:27)
    at kotlin.Function1#invoke(/Users/casvanluijtelaar/.gradle/daemon/8.7/[K][Suspend]Functions:1)
    at dev.mokkery.internal#internalBaseVerify(/Users/damianbaczynski/IdeaProjects/Mokkery/mokkery-runtime/src/commonMain/kotlin/dev/mokkery/internal/Verify.kt:45)
    at dev.mokkery.internal#internalVerify(/Users/damianbaczynski/IdeaProjects/Mokkery/mokkery-runtime/src/commonMain/kotlin/dev/mokkery/internal/Verify.kt:41)
    at dev.mokkery.internal#internalVerifySuspend(/Users/damianbaczynski/IdeaProjects/Mokkery/mokkery-runtime/src/commonMain/kotlin/dev/mokkery/internal/Verify.kt:27)
    at com.x.presentation.marketpicker.$prefilledParamThrowsKotlinClassCastException$lambda$2COROUTINE$1.invokeSuspend#internal(/Users/casvanluijtelaar/Projects/x-gmalite-multiplatform/x-marketpicker-module/src/commonTest/kotlin/com/x/presentation/marketpicker/MarketPickerViewModelTests.kt:543)
    at com.x.presentation.marketpicker.prefilledParamThrowsKotlinClassCastException$lambda$2#internal(/Users/casvanluijtelaar/Projects/x-gmalite-multiplatform/x-marketpicker-module/src/commonTest/kotlin/com/x/presentation/marketpicker/MarketPickerViewModelTests.kt:532)
    at com.x.presentation.marketpicker.$prefilledParamThrowsKotlinClassCastException$lambda$2$FUNCTION_REFERENCE$101.invoke#internal(/Users/casvanluijtelaar/Projects/x-gmalite-multiplatform/x-marketpicker-module/src/commonTest/kotlin/com/x/presentation/marketpicker/MarketPickerViewModelTests.kt:532)
    at kotlin.coroutines.SuspendFunction1#invoke#suspend(/Users/casvanluijtelaar/.gradle/daemon/8.7/[K][Suspend]Functions:1)
    at kotlinx.coroutines.test.$runTest$lambda$6$lambda$0COROUTINE$1.invokeSuspend#internal(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-test/common/src/TestBuilders.kt:318)
    at kotlin.coroutines.native.internal.BaseContinuationImpl#invokeSuspend(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:50)
    at kotlin.coroutines.native.internal.BaseContinuationImpl#resumeWith(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:30)
    at kotlin.coroutines.Continuation#resumeWith(/opt/buildAgent/work/ed783494cd2364bc/kotlin/libraries/stdlib/src/kotlin/coroutines/Continuation.kt:26)
    at kotlinx.coroutines.DispatchedTask#run(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt:101)
    at kotlinx.coroutines.Runnable#run(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Runnable.kt:10)
    at kotlinx.coroutines.test.TestDispatcher#processEvent(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-test/common/src/TestDispatcher.kt:24)
    at kotlinx.coroutines.test.TestCoroutineScheduler#tryRunNextTaskUnless(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-test/common/src/TestCoroutineScheduler.kt:99)
    at kotlinx.coroutines.test.$runTest$lambda$6$lambda$2COROUTINE$2.invokeSuspend#internal(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-test/common/src/TestBuilders.kt:327)
    at kotlinx.coroutines.test.runTest$lambda$6$lambda$2#internal(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-test/common/src/TestBuilders.kt:325)
    at kotlinx.coroutines.test.$runTest$lambda$6$lambda$2$FUNCTION_REFERENCE$13.invoke#internal(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-test/common/src/TestBuilders.kt:325)
    at kotlin.Function2#invoke(/Users/casvanluijtelaar/.gradle/daemon/8.7/[K][Suspend]Functions:1)
    at kotlin.coroutines.intrinsics.object-4.invokeSuspend#internal(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/intrinsics/IntrinsicsNative.kt:254)
    at kotlin.coroutines.native.internal.BaseContinuationImpl#invokeSuspend(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:50)
    at kotlin.coroutines.native.internal.BaseContinuationImpl#resumeWith(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/coroutines/ContinuationImpl.kt:30)
    at kotlin.coroutines.Continuation#resumeWith(/opt/buildAgent/work/ed783494cd2364bc/kotlin/libraries/stdlib/src/kotlin/coroutines/Continuation.kt:26)
    at kotlinx.coroutines.DispatchedTask#run(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/internal/DispatchedTask.kt:101)
    at kotlinx.coroutines.Runnable#run(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Runnable.kt:10)
    at kotlinx.coroutines.EventLoopImplBase#processNextEvent(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/EventLoop.common.kt:263)
    at kotlinx.coroutines.EventLoop#processNextEvent(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/common/src/EventLoop.common.kt:49)
    at kotlinx.coroutines.BlockingCoroutine.joinBlocking#internal(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Builders.kt:129)
    at kotlinx.coroutines#runBlocking(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Builders.kt:68)
    at kotlinx.coroutines#runBlocking$default(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-core/native/src/Builders.kt:45)
    at kotlinx.coroutines.test#createTestResult(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-test/native/src/TestBuilders.kt:8)
    at kotlinx.coroutines.test#runTest__at__kotlinx.coroutines.test.TestScope(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-test/common/src/TestBuilders.kt:310)
    at kotlinx.coroutines.test#runTest(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-test/common/src/TestBuilders.kt:168)
    at kotlinx.coroutines.test#runTest$default(/opt/buildAgent/work/44ec6e850d5c63f0/kotlinx-coroutines-test/common/src/TestBuilders.kt:160)
    at com.x.presentation.marketpicker#prefilledParamThrowsKotlinClassCastException(/Users/casvanluijtelaar/Projects/x-gmalite-multiplatform/x-marketpicker-module/src/commonTest/kotlin/com/x/presentation/marketpicker/MarketPickerViewModelTests.kt:532)
    at com.x.presentation.marketpicker.$prefilledParamThrowsKotlinClassCastException$FUNCTION_REFERENCE$123.invoke#internal(/Users/casvanluijtelaar/Projects/x-gmalite-multiplatform/x-marketpicker-module/src/commonTest/kotlin/com/x/presentation/marketpicker/MarketPickerViewModelTests.kt:531)
    at com.x.presentation.marketpicker.$prefilledParamThrowsKotlinClassCastException$FUNCTION_REFERENCE$123.$<bridge-DNN>invoke(/Users/casvanluijtelaar/Projects/x-gmalite-multiplatform/x-marketpicker-module/src/commonTest/kotlin/com/x/presentation/marketpicker/MarketPickerViewModelTests.kt:531)
    at kotlin.Function0#invoke(/Users/casvanluijtelaar/.gradle/daemon/8.7/[K][Suspend]Functions:1)
    at kotlin.native.internal.test.TopLevelSuite.TestCase#doRun(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/test/TestSuite.kt:240)
    at kotlin.native.internal.test.TestCase#doRun(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/test/TestSuite.kt:44)
    at kotlin.native.internal.test.TestCase#run(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/test/TestSuite.kt:52)
    at kotlin.native.internal.test.TestCase#run(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/test/TestSuite.kt:49)
    at kotlin.native.internal.test.TestRunner.run#internal(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/test/TestRunner.kt:263)
    at kotlin.native.internal.test.TestRunner.runIteration#internal(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/test/TestRunner.kt:289)
    at kotlin.native.internal.test.TestRunner#run(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/test/TestRunner.kt:304)
    at kotlin.native.internal.test#testLauncherEntryPoint(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/test/Launcher.kt:33)
    at kotlin.native.internal.test#main(/opt/buildAgent/work/ed783494cd2364bc/kotlin/kotlin-native/runtime/src/main/kotlin/kotlin/native/internal/test/Launcher.kt:38)
    at <global>.Konan_start(/Users/casvanluijtelaar/.gradle/daemon/8.7/entryPointOwner:1)
    at <global>.Init_and_run_start(Unknown Source)
    at <global>.0x0(Unknown Source)
    at <global>.0x0(Unknown Source)
    at <global>.0x0(Unknown Source)

This might not be the minimum reproducible example, but it will reliably fail:

interface TestInterface {
    suspend fun testMethodWithPrefilledParam(
        requiredParam: Boolean,
        prefilledParam: Boolean = true,
    ): TestAnswer
}

class TestClass(
    private val testInterface: TestInterface,
) {
    suspend fun methodToTest(requiredParam: Boolean) {
        testInterface.testMethodWithPrefilledParam(requiredParam)
    }
}

data class TestAnswer(val value: Int)

@Test
fun prefilledParamThrowsKotlinClassCastException() = runTest {
    // given
    val mock = mock<TestInterface>()
    everySuspend { mock.testMethodWithPrefilledParam(true) }.returns(TestAnswer(1))
    val testClass = TestClass(mock)

    // when
    testClass.methodToTest(true)

    // then
    verifySuspend { mock.testMethodWithPrefilledParam(requiredParam = true) }
}

this test fails on iOS but passes on Android. It can be resolved by changing

  verifySuspend { mock.testMethodWithPrefilledParam(requiredParam = true) }

to

  verifySuspend { mock.testMethodWithPrefilledParam(requiredParam = true, any()) }

but still seems like unexpected behavior

lupuuss commented 1 month ago

I was able to reproduce it. I will try to include the fix in the same release as #43, but I cannot guarantee it as this bug seems to be much harder to fix. Thank you for reporting the issue 🙏

lupuuss commented 1 month ago

Fixed in 2.4.0

casvanluijtelaar commented 1 month ago

can confirm fixed. great library, good job 👍