diff --git a/modules/mockk-core/src/jvmMain/kotlin/io/mockk/core/ValueClassSupport.kt b/modules/mockk-core/src/jvmMain/kotlin/io/mockk/core/ValueClassSupport.kt index cf7097250..393d72a65 100644 --- a/modules/mockk-core/src/jvmMain/kotlin/io/mockk/core/ValueClassSupport.kt +++ b/modules/mockk-core/src/jvmMain/kotlin/io/mockk/core/ValueClassSupport.kt @@ -2,14 +2,15 @@ package io.mockk.core import java.lang.reflect.Method import kotlin.reflect.KClass +import kotlin.reflect.KProperty import kotlin.reflect.KProperty1 import kotlin.reflect.full.declaredMemberProperties +import kotlin.reflect.jvm.internal.KotlinReflectionInternalError import kotlin.reflect.jvm.isAccessible import kotlin.reflect.jvm.javaField -import kotlin.reflect.jvm.internal.KotlinReflectionInternalError +import kotlin.reflect.jvm.javaGetter import kotlin.reflect.jvm.kotlinFunction - actual object ValueClassSupport { /** @@ -23,20 +24,38 @@ actual object ValueClassSupport { if (!resultType.isValue_safe) { return this } - val kFunction = method.kotlinFunction ?: return this - - // Only unbox a value class if the method's return type is actually the type of the inlined property. - // For example, in a normal case where a value class `Foo` with underlying `Int` property is inlined: - // method.returnType == int (the actual representation of inlined property on JVM) - // method.kotlinFunction.returnType.classifier == Foo - val expectedReturnType = kFunction.returnType.classifier - return if (resultType == expectedReturnType) { - this.boxedValue + val kFunction = method.kotlinFunction + if (kFunction != null) { + // Only unbox a value class if the method's return type is actually the type of the inlined property. + // For example, in a normal case where a value class `Foo` with underlying `Int` property is inlined: + // method.returnType == int (the actual representation of inlined property on JVM) + // method.kotlinFunction.returnType.classifier == Foo + val expectedReturnType = kFunction.returnType.classifier + return if (resultType == expectedReturnType) { + this.boxedValue + } else { + this + } + } + // It is possible that the method is a getter for a property, in which case we can check the property's return + // type in kotlin. + val kProperty = findMatchingPropertyWithJavaGetter(method) + if (kProperty == null) { + return this } else { - this + val expectedReturnType = kProperty.returnType.classifier + return if (resultType == expectedReturnType) { + this.boxedValue + } else { + this + } } } + private fun findMatchingPropertyWithJavaGetter(method: Method): KProperty<*>? { + return method.declaringClass.kotlin.declaredMemberProperties.find { it.javaGetter == method } + } + /** * Underlying property value of a **`value class`** or self. * @@ -95,5 +114,4 @@ actual object ValueClassSupport { } catch (_: AbstractMethodError) { false } - } diff --git a/modules/mockk/src/commonTest/kotlin/io/mockk/it/ValueClassTest.kt b/modules/mockk/src/commonTest/kotlin/io/mockk/it/ValueClassTest.kt index aadccdd6d..b1628af82 100644 --- a/modules/mockk/src/commonTest/kotlin/io/mockk/it/ValueClassTest.kt +++ b/modules/mockk/src/commonTest/kotlin/io/mockk/it/ValueClassTest.kt @@ -1,10 +1,13 @@ package io.mockk.it -import io.mockk.* +import io.mockk.every +import io.mockk.mockk +import io.mockk.slot +import io.mockk.spyk +import io.mockk.verify import org.junit.jupiter.api.assertTimeoutPreemptively import java.time.Duration import java.util.UUID -import kotlin.jvm.JvmInline import kotlin.test.Ignore import kotlin.test.Test import kotlin.test.assertEquals @@ -31,6 +34,17 @@ class ValueClassTest { verify { mock.argValueClassReturnValueClass(dummyValueClassArg) } } + @Test + fun `field is ValueClass, returns ValueClass`() { + val mock = mockk { + every { valueClassField } returns dummyValueClassReturn + } + + assertEquals(dummyValueClassReturn, mock.valueClassField) + + verify { mock.valueClassField } + } + @Test fun `arg is any(ValueClass), returns ValueClass`() { val mock = mockk { @@ -625,6 +639,7 @@ class ValueClassTest { @Suppress("UNUSED_PARAMETER") class DummyService { + val valueClassField = DummyValue(0) fun argWrapperReturnWrapper(wrapper: DummyValueWrapper): DummyValueWrapper = DummyValueWrapper(DummyValue(0))