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

Only unbox value class answers when appropriate #1204

Merged
merged 2 commits into from
Jan 16, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package io.mockk.proxy.jvm.advice

import io.mockk.core.ValueClassSupport.boxedValue
import io.mockk.core.ValueClassSupport.maybeUnboxValueForMethodReturn
import io.mockk.proxy.MockKInvocationHandler
import java.lang.reflect.Method
import java.util.concurrent.Callable
Expand All @@ -19,7 +19,7 @@ internal class Interceptor(
method
)
return handler.invocation(self, method, callOriginalMethod, arguments)
?.boxedValue // unbox value class objects
?.maybeUnboxValueForMethodReturn(method)
}

}
1 change: 1 addition & 0 deletions modules/mockk-core/api/mockk-core.api
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,6 @@ public final class io/mockk/core/ValueClassSupport {
public static final field INSTANCE Lio/mockk/core/ValueClassSupport;
public final fun getBoxedClass (Lkotlin/reflect/KClass;)Lkotlin/reflect/KClass;
public final fun getBoxedValue (Ljava/lang/Object;)Ljava/lang/Object;
public final fun maybeUnboxValueForMethodReturn (Ljava/lang/Object;Ljava/lang/reflect/Method;)Ljava/lang/Object;
}

Original file line number Diff line number Diff line change
@@ -1,10 +1,19 @@
package io.mockk.core

import java.lang.reflect.Method
import kotlin.reflect.KClass


expect object ValueClassSupport {

/**
* Unboxes the underlying property value of a **`value class`** or self, as long the unboxed value is appropriate
* for the given method's return type.
*
* @see boxedValue
*/
fun <T : Any> T.maybeUnboxValueForMethodReturn(method: Method): Any?

/**
* Underlying property value of a **`value class`** or self.
*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,42 @@
package io.mockk.core

import java.lang.reflect.Method
import kotlin.reflect.KClass
import kotlin.reflect.KProperty1
import kotlin.reflect.full.declaredMemberProperties
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaField
import kotlin.reflect.jvm.internal.KotlinReflectionInternalError
import kotlin.reflect.jvm.kotlinFunction


actual object ValueClassSupport {

/**
* Unboxes the underlying property value of a **`value class`** or self, as long the unboxed value is appropriate
* for the given method's return type.
*
* @see boxedValue
*/
actual fun <T : Any> T.maybeUnboxValueForMethodReturn(method: Method): Any? {
val resultType = this::class
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
} else {
this
}
}

/**
* Underlying property value of a **`value class`** or self.
*
Expand Down
20 changes: 20 additions & 0 deletions modules/mockk/src/commonTest/kotlin/io/mockk/it/ValueClassTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -585,6 +585,23 @@ class ValueClassTest {
}
}

@Test
fun `spy class returning value class not boxed due to cast to another type`() {
val f = spyk<DummyService>()
val result = f.returnValueClassNotInlined() as DummyValue

assertEquals(DummyValue(0), result)
}

@Test
fun `mock class returning value class not boxed due to cast to another type`() {
val f = mockk<DummyService>()
every { f.returnValueClassNotInlined() } returns DummyValue(3)
val result = f.returnValueClassNotInlined() as DummyValue

assertEquals(DummyValue(3), result)
}

companion object {

@JvmInline
Expand Down Expand Up @@ -622,6 +639,9 @@ class ValueClassTest {
fun returnValueClass(): DummyValue =
DummyValue(0)

// Note the value class is not inlined in this case due to being cast to another type
fun returnValueClassNotInlined(): Any = DummyValue(0)

fun argNoneReturnsUInt(): UInt = 123u
}
}
Expand Down