Skip to content

Commit ae8905b

Browse files
jselboJoshua Selbo
andauthored
Fix anyOrNull matcher with value class wrapping primitive type (#557)
Co-authored-by: Joshua Selbo <jselbo@meta.com>
1 parent b4f703a commit ae8905b

File tree

4 files changed

+105
-7
lines changed

4 files changed

+105
-7
lines changed

mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/CreateInstance.kt

Lines changed: 23 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,16 @@
2626
package org.mockito.kotlin.internal
2727

2828
import kotlin.reflect.KClass
29+
import kotlin.reflect.KProperty1
30+
import kotlin.reflect.full.primaryConstructor
2931

3032
inline fun <reified T : Any> createInstance(): T {
31-
return when (T::class) {
33+
return createInstance(T::class)
34+
}
35+
36+
@Suppress("UNCHECKED_CAST")
37+
fun <T : Any> createInstance(kClass: KClass<T>): T {
38+
return when (kClass) {
3239
Boolean::class -> false as T
3340
Byte::class -> 0.toByte() as T
3441
Char::class -> 0.toChar() as T
@@ -37,16 +44,17 @@ inline fun <reified T : Any> createInstance(): T {
3744
Long::class -> 0L as T
3845
Float::class -> 0f as T
3946
Double::class -> 0.0 as T
40-
else -> createInstance(T::class)
47+
else -> createInstanceNonPrimitive(kClass)
4148
}
4249
}
4350

4451
@Suppress("UNCHECKED_CAST")
45-
fun <T : Any> createInstance(@Suppress("UNUSED_PARAMETER") kClass: KClass<T>): T {
46-
return if(kClass.isValue) {
47-
val boxImpl =
48-
kClass.java.declaredMethods.single { it.name == "box-impl" && it.parameterCount == 1 }
49-
boxImpl.invoke(null, castNull()) as T
52+
private fun <T : Any> createInstanceNonPrimitive(kClass: KClass<T>): T {
53+
return if (kClass.isValue) {
54+
val boxImpl =
55+
kClass.java.declaredMethods.single { it.name == "box-impl" && it.parameterCount == 1 }
56+
val wrappedType = getValueClassWrappedType(kClass)
57+
boxImpl.invoke(null, createInstance(wrappedType)) as T
5058
} else {
5159
castNull()
5260
}
@@ -60,3 +68,11 @@ fun <T : Any> createInstance(@Suppress("UNUSED_PARAMETER") kClass: KClass<T>): T
6068
*/
6169
@Suppress("UNCHECKED_CAST")
6270
private fun <T> castNull(): T = null as T
71+
72+
private fun getValueClassWrappedType(kClass: KClass<*>): KClass<*> {
73+
require(kClass.isValue)
74+
75+
val primaryConstructor = checkNotNull(kClass.primaryConstructor)
76+
val wrappedType = primaryConstructor.parameters.single().type
77+
return wrappedType.classifier as KClass<*>
78+
}

tests/src/test/kotlin/test/ArgumentCaptorTest.kt

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package test
22

33
import com.nhaarman.expect.expect
44
import com.nhaarman.expect.expectErrorWithMessage
5+
import org.junit.Ignore
56
import org.junit.Test
67
import org.mockito.kotlin.*
78
import java.util.*
@@ -382,4 +383,36 @@ class ArgumentCaptorTest : TestBase() {
382383
verify(m).nullableValueClass(captor.capture())
383384
expect(captor.firstValue).toBeNull()
384385
}
386+
387+
@Test
388+
@Ignore("See issue #555")
389+
fun argumentCaptor_primitive_value_class() {
390+
/* Given */
391+
val m: SynchronousFunctions = mock()
392+
val valueClass = PrimitiveValueClass(123)
393+
394+
/* When */
395+
m.primitiveValueClass(valueClass)
396+
397+
/* Then */
398+
val captor = argumentCaptor<PrimitiveValueClass>()
399+
verify(m).primitiveValueClass(captor.capture())
400+
expect(captor.firstValue).toBe(valueClass)
401+
}
402+
403+
@Test
404+
@Ignore("See issue #555")
405+
fun argumentCaptor_nullable_primitive_value_class() {
406+
/* Given */
407+
val m: SynchronousFunctions = mock()
408+
val valueClass = PrimitiveValueClass(123)
409+
410+
/* When */
411+
m.nullablePrimitiveValueClass(valueClass)
412+
413+
/* Then */
414+
val captor = argumentCaptor<PrimitiveValueClass>()
415+
verify(m).nullablePrimitiveValueClass(captor.capture())
416+
expect(captor.firstValue).toBe(valueClass)
417+
}
385418
}

tests/src/test/kotlin/test/Classes.kt

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,8 @@ interface SynchronousFunctions {
101101
fun valueClassResult(): ValueClass
102102
fun nullableValueClassResult(): ValueClass?
103103
fun nestedValueClassResult(): NestedValueClass
104+
fun primitiveValueClass(v: PrimitiveValueClass)
105+
fun nullablePrimitiveValueClass(v: PrimitiveValueClass?)
104106
}
105107

106108
interface SuspendFunctions {
@@ -125,6 +127,9 @@ value class ValueClass(val content: String)
125127
@JvmInline
126128
value class NestedValueClass(val value: ValueClass)
127129

130+
@JvmInline
131+
value class PrimitiveValueClass(val value: Long)
132+
128133
interface ExtraInterface
129134

130135
abstract class ThrowingConstructor {

tests/src/test/kotlin/test/MatchersTest.kt

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package test
33
import com.nhaarman.expect.expect
44
import com.nhaarman.expect.expectErrorWithMessage
55
import kotlinx.coroutines.test.runTest
6+
import org.junit.Ignore
67
import org.junit.Test
78
import org.junit.experimental.runners.Enclosed
89
import org.junit.runner.RunWith
@@ -201,6 +202,14 @@ class MatchersTest : TestBase() {
201202
}
202203
}
203204

205+
@Test
206+
fun anyPrimitiveValueClass() {
207+
mock<SynchronousFunctions>().apply {
208+
primitiveValueClass(PrimitiveValueClass(123))
209+
verify(this).primitiveValueClass(any())
210+
}
211+
}
212+
204213
@Test
205214
fun anyNeverVerifiesForNullValue() {
206215
mock<SynchronousFunctions>().apply {
@@ -383,6 +392,22 @@ class MatchersTest : TestBase() {
383392
verify(this).nullableValueClass(anyOrNull())
384393
}
385394
}
395+
396+
@Test
397+
fun anyOrNullNullablePrimitiveValueClass() {
398+
mock<SynchronousFunctions>().apply {
399+
nullablePrimitiveValueClass(PrimitiveValueClass(123))
400+
verify(this).nullablePrimitiveValueClass(anyOrNull())
401+
}
402+
}
403+
404+
@Test
405+
fun anyOrNullNullablePrimitiveValueClassNullValue() {
406+
mock<SynchronousFunctions>().apply {
407+
nullablePrimitiveValueClass(null)
408+
verify(this).nullablePrimitiveValueClass(anyOrNull())
409+
}
410+
}
386411
}
387412

388413
class EqMatchersTest {
@@ -594,6 +619,25 @@ class MatchersTest : TestBase() {
594619
}
595620
}
596621

622+
@Test
623+
fun eqNullableValueClass() {
624+
val valueClass = ValueClass("Content")
625+
mock<SynchronousFunctions>().apply {
626+
nullableValueClass(valueClass)
627+
verify(this).nullableValueClass(eq(valueClass))
628+
}
629+
}
630+
631+
@Test
632+
@Ignore("See issue #555")
633+
fun eqNullablePrimitiveValueClass() {
634+
val valueClass = PrimitiveValueClass(123)
635+
mock<SynchronousFunctions>().apply {
636+
nullablePrimitiveValueClass(valueClass)
637+
verify(this).nullablePrimitiveValueClass(eq(valueClass))
638+
}
639+
}
640+
597641
@Test
598642
fun eqNestedValueClass() {
599643
val nestedValueClass = NestedValueClass(ValueClass("Content"))

0 commit comments

Comments
 (0)