Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Kotlin - Unable to return value classes #456

Open
kargath opened this issue Mar 15, 2022 · 6 comments
Open

Kotlin - Unable to return value classes #456

kargath opened this issue Mar 15, 2022 · 6 comments

Comments

@kargath
Copy link

kargath commented Mar 15, 2022

When returning a value class from a mocked method, class cast can not be done properly

Sample code:

value class Base64Data(val data: String)
fun doSomething(data: Base64Data): Base64Data = Base64Data("test")

/**
 * Workaround to support matching of value classes
 *
 * From: https://github.com/mockito/mockito-kotlin/issues/445#issuecomment-983619131
 */
inline fun <Outer, reified Inner> eqValueClass(
    expected: Outer,
    crossinline access: (Outer) -> Inner,
    test: ((actual: Any) -> Boolean) -> Any? = ::argWhere
): Outer {
    val assertion: (Any) -> Boolean = { actual ->
        if (actual is Inner) {
            access(expected) == actual
        } else {
            expected == actual
        }
    }
    @Suppress("UNCHECKED_CAST")
    return test(assertion) as Outer? ?: expected
}

    @Test
    fun `test something`() = runTest {
        // GIVEN
        val screenshotEncoded = Base64Data("screenshot-encoded")
        whenever(
            client.doSomething(
               eqValueClass(levelData, { it.data })
            )
        ) doReturn screenshotEncoded

        // WHEN
        {...}
}

When executing that code and getting the mock the response is as follows:

java.lang.ClassCastException: class com.king.uplevelmanager.util.Base64Data cannot be cast to class java.lang.String (com.king.uplevelmanager.util.Base64Data is in unnamed module of loader 'app'; java.lang.String is in module java.base of loader 'bootstrap')

Tested returning a simple type like String (to discard matcher failing) and worked successfully.

@TimvdLippe TimvdLippe transferred this issue from mockito/mockito Mar 16, 2022
@kargath
Copy link
Author

kargath commented Mar 16, 2022

Update: The issue seems to arise only when the mocked method is used within a runBlocking corroutine.

When returning the mock inside a "normal" piece of code, the mocking is done properly.

@kargath
Copy link
Author

kargath commented Mar 16, 2022

I'm adding a fully functional test:

import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.test.runTest
import org.amshove.kluent.`should be equal to`
import org.junit.jupiter.api.Test
import org.mockito.kotlin.argWhere
import org.mockito.kotlin.doReturn
import org.mockito.kotlin.mock
import org.mockito.kotlin.whenever

class SimpleTest2 {

    private val instance = MockSuspendClassImpl()
    private val mockedInstance: MockSuspendClass = mock()

    @Test
    fun `test with corroutines simple - no mock`() {
        // GIVEN
        val data = Data("something")
        val unit = UnderTest(instance)

        // WHEN
        val ret = unit.doSomethingPlain(data)

        // THEN
        ret `should be equal to` data.data
    }

    @Test
    fun `test with corroutines data - no mock`() {
        // GIVEN
        val data = Data("something")
        val unit = UnderTest(instance)

        // WHEN
        val ret = unit.doSomethingValue(data)

        // THEN
        ret `should be equal to` data
    }

    @Test
    fun `test with corroutines simple - mock`() = runTest {
        // GIVEN
        val data = Data("something")
        val unit = UnderTest(mockedInstance)
        whenever(mockedInstance.printData(eqValueClass(data, { it.data }))) doReturn data.data

        // WHEN
        val ret = unit.doSomethingPlain(data)

        // THEN
        ret `should be equal to` data.data
    }

    @Test
    fun `test with corroutines data - mock`() = runTest {
        // GIVEN
        val data = Data("something")
        val unit = UnderTest(mockedInstance)
        whenever(mockedInstance.printDataReturn(eqValueClass(data, { it.data }))) doReturn data

        // WHEN
        val ret = unit.doSomethingValue(data)

        // THEN
        ret `should be equal to` data
    }
}

class UnderTest(val mockedClass: MockSuspendClass) {
    fun doSomethingPlain(data: Data): String {
        return runBlocking(Dispatchers.Default) { mockedClass.printData(data) }
    }

    fun doSomethingValue(data: Data): Data {
        return runBlocking(Dispatchers.Default) { mockedClass.printDataReturn(data) }
    }
}

interface MockSuspendClass {
    suspend fun printData(data: Data): String
    suspend fun printDataReturn(data: Data): Data
}

class MockSuspendClassImpl : MockSuspendClass {
    override suspend fun printData(data: Data): String {
        println("Data is $data")
        delay(10)
        return data.data
    }

    override suspend fun printDataReturn(data: Data): Data {
        println("Data is $data")
        delay(10)
        return data
    }
}

@JvmInline
value class Data(val data: String)

/**
 * Workaround to support matching of value classes
 *
 * From: https://github.com/mockito/mockito-kotlin/issues/445#issuecomment-983619131
 */
inline fun <Outer, reified Inner> eqValueClass(
    expected: Outer,
    crossinline access: (Outer) -> Inner,
    test: ((actual: Any) -> Boolean) -> Any? = ::argWhere
): Outer {
    val assertion: (Any) -> Boolean = { actual ->
        if (actual is Inner) {
            access(expected) == actual
        } else {
            expected == actual
        }
    }
    @Suppress("UNCHECKED_CAST")
    return test(assertion) as Outer? ?: expected
}

@fat-fellow
Copy link

Related issue https://youtrack.jetbrains.com/issue/KT-51641

@renannprado
Copy link

does anybody have a workaround for this?

I'm trying to use any currently, not even eq.

@BernatCarbo
Copy link

Same issue here, it's really annoying.

@foster
Copy link

foster commented Mar 30, 2024

For anyone else who found their way here, I found a workaround to using eq() and any() for an inline value class in #309

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

5 participants