Skip to content

Commit d2b1166

Browse files
authored
Follow-up on primitive value classes (#561)
* Fix handling of primitive value class arguments in eq matcher and argumentCaptor. * Extract all value class handling (type deduction, boxing, unboxing, etc.) to ValueClassSupport.kt * Make Matchers.eqValueClass() a bit more lenient for value class arguments: let the matcher match with either boxed or unboxed value as actual call argument. * Fix KArgumentCaptor for incompatibility with suspend function arguments.
1 parent 4acf020 commit d2b1166

File tree

10 files changed

+516
-130
lines changed

10 files changed

+516
-130
lines changed

mockito-kotlin/src/main/kotlin/org/mockito/kotlin/ArgumentCaptor.kt

Lines changed: 123 additions & 75 deletions
Original file line numberDiff line numberDiff line change
@@ -25,43 +25,84 @@
2525

2626
package org.mockito.kotlin
2727

28-
import org.mockito.kotlin.internal.createInstance
2928
import org.mockito.ArgumentCaptor
29+
import org.mockito.kotlin.internal.toKotlinType
30+
import org.mockito.kotlin.internal.createInstance
31+
import org.mockito.kotlin.internal.valueClassInnerClass
3032
import java.lang.reflect.Array
3133
import kotlin.reflect.KClass
34+
import kotlin.reflect.KType
35+
import kotlin.reflect.typeOf
3236

3337
/**
3438
* Creates a [KArgumentCaptor] for given type.
39+
*
40+
* Caution: this factory method cannot be used to create a captor for a suspend
41+
* function, please refer to [suspendFunctionArgumentCaptor] for that.
42+
* This incompatibility is caused by the use of `typeOf<T>()` which is the way
43+
* to determine runtime nullability of T, but this function is yet incompatible with
44+
* suspend functions at compile time. That incompatibility has been declared since
45+
* Kotlin 1.6, and the promised proper support for suspend functions has not been
46+
* delivered ever since.
47+
* See [Kotlin issue KT-47562](https://youtrack.jetbrains.com/issue/KT-47562/Support-suspend-functional-types-in-typeOf)
48+
* for more details.
49+
*/
50+
inline fun <reified T : Any?> argumentCaptor(): KArgumentCaptor<T> {
51+
return KArgumentCaptor(typeOf<T>())
52+
}
53+
54+
/**
55+
* Creates a [KArgumentCaptor] for given (suspend function).
3556
*/
36-
inline fun <reified T : Any> argumentCaptor(): KArgumentCaptor<T> {
57+
inline fun <reified T : Function<*>> suspendFunctionArgumentCaptor(): KArgumentCaptor<T> {
3758
return KArgumentCaptor(T::class)
3859
}
3960

4061
/**
4162
* Creates 2 [KArgumentCaptor]s for given types.
63+
*
64+
* Caution: this factory method cannot be used to create a captor for a suspend
65+
* function, please refer to [suspendFunctionArgumentCaptor] for that.
66+
* This incompatibility is caused by the use of `typeOf<T>()` which is the way
67+
* to determine runtime nullability of T, but this function is yet incompatible with
68+
* suspend functions at compile time. That incompatibility has been declared since
69+
* Kotlin 1.6, and the promised proper support for suspend functions has not been
70+
* delivered ever since.
71+
* See [Kotlin issue KT-47562](https://youtrack.jetbrains.com/issue/KT-47562/Support-suspend-functional-types-in-typeOf)
72+
* for more details.
4273
*/
4374
inline fun <reified A : Any, reified B : Any> argumentCaptor(
44-
a: KClass<A> = A::class,
45-
b: KClass<B> = B::class
75+
@Suppress("unused") a: KClass<A> = A::class,
76+
@Suppress("unused") b: KClass<B> = B::class
4677
): Pair<KArgumentCaptor<A>, KArgumentCaptor<B>> {
4778
return Pair(
48-
KArgumentCaptor(a),
49-
KArgumentCaptor(b)
79+
KArgumentCaptor(typeOf<A>()),
80+
KArgumentCaptor(typeOf<B>())
5081
)
5182
}
5283

5384
/**
5485
* Creates 3 [KArgumentCaptor]s for given types.
86+
*
87+
* Caution: this factory method cannot be used to create a captor for a suspend
88+
* function, please refer to [suspendFunctionArgumentCaptor] for that.
89+
* This incompatibility is caused by the use of `typeOf<T>()` which is the way
90+
* to determine runtime nullability of T, but this function is yet incompatible with
91+
* suspend functions at compile time. That incompatibility has been declared since
92+
* Kotlin 1.6, and the promised proper support for suspend functions has not been
93+
* delivered ever since.
94+
* See [Kotlin issue KT-47562](https://youtrack.jetbrains.com/issue/KT-47562/Support-suspend-functional-types-in-typeOf)
95+
* for more details.
5596
*/
5697
inline fun <reified A : Any, reified B : Any, reified C : Any> argumentCaptor(
57-
a: KClass<A> = A::class,
58-
b: KClass<B> = B::class,
59-
c: KClass<C> = C::class
98+
@Suppress("unused") a: KClass<A> = A::class,
99+
@Suppress("unused") b: KClass<B> = B::class,
100+
@Suppress("unused") c: KClass<C> = C::class
60101
): Triple<KArgumentCaptor<A>, KArgumentCaptor<B>, KArgumentCaptor<C>> {
61102
return Triple(
62-
KArgumentCaptor(a),
63-
KArgumentCaptor(b),
64-
KArgumentCaptor(c)
103+
KArgumentCaptor(typeOf<A>()),
104+
KArgumentCaptor(typeOf<B>()),
105+
KArgumentCaptor(typeOf<C>())
65106
)
66107
}
67108

@@ -71,21 +112,46 @@ class ArgumentCaptorHolder4<out A, out B, out C, out D>(
71112
val third: C,
72113
val fourth: D
73114
) {
74-
75115
operator fun component1() = first
76116
operator fun component2() = second
77117
operator fun component3() = third
78118
operator fun component4() = fourth
79119
}
80120

121+
/**
122+
* Creates 4 [KArgumentCaptor]s for given types.
123+
*
124+
* Caution: this factory method cannot be used to create a captor for a suspend
125+
* function, please refer to [suspendFunctionArgumentCaptor] for that.
126+
* This incompatibility is caused by the use of `typeOf<T>()` which is the way
127+
* to determine runtime nullability of T, but this function is yet incompatible with
128+
* suspend functions at compile time. That incompatibility has been declared since
129+
* Kotlin 1.6, and the promised proper support for suspend functions has not been
130+
* delivered ever since.
131+
* See [Kotlin issue KT-47562](https://youtrack.jetbrains.com/issue/KT-47562/Support-suspend-functional-types-in-typeOf)
132+
* for more details.
133+
*/
134+
inline fun <reified A : Any, reified B : Any, reified C : Any, reified D : Any> argumentCaptor(
135+
@Suppress("unused") a: KClass<A> = A::class,
136+
@Suppress("unused") b: KClass<B> = B::class,
137+
@Suppress("unused") c: KClass<C> = C::class,
138+
@Suppress("unused") d: KClass<D> = D::class
139+
): ArgumentCaptorHolder4<KArgumentCaptor<A>, KArgumentCaptor<B>, KArgumentCaptor<C>, KArgumentCaptor<D>> {
140+
return ArgumentCaptorHolder4(
141+
KArgumentCaptor(typeOf<A>()),
142+
KArgumentCaptor(typeOf<B>()),
143+
KArgumentCaptor(typeOf<C>()),
144+
KArgumentCaptor(typeOf<D>())
145+
)
146+
}
147+
81148
class ArgumentCaptorHolder5<out A, out B, out C, out D, out E>(
82149
val first: A,
83150
val second: B,
84151
val third: C,
85152
val fourth: D,
86153
val fifth: E
87154
) {
88-
89155
operator fun component1() = first
90156
operator fun component2() = second
91157
operator fun component3() = third
@@ -94,38 +160,31 @@ class ArgumentCaptorHolder5<out A, out B, out C, out D, out E>(
94160
}
95161

96162
/**
97-
* Creates 4 [KArgumentCaptor]s for given types.
98-
*/
99-
inline fun <reified A : Any, reified B : Any, reified C : Any, reified D : Any> argumentCaptor(
100-
a: KClass<A> = A::class,
101-
b: KClass<B> = B::class,
102-
c: KClass<C> = C::class,
103-
d: KClass<D> = D::class
104-
): ArgumentCaptorHolder4<KArgumentCaptor<A>, KArgumentCaptor<B>, KArgumentCaptor<C>, KArgumentCaptor<D>> {
105-
return ArgumentCaptorHolder4(
106-
KArgumentCaptor(a),
107-
KArgumentCaptor(b),
108-
KArgumentCaptor(c),
109-
KArgumentCaptor(d)
110-
)
111-
}
112-
113-
/**
114-
* Creates 4 [KArgumentCaptor]s for given types.
163+
* Creates 5 [KArgumentCaptor]s for given types.
164+
*
165+
* Caution: this factory method cannot be used to create a captor for a suspend
166+
* function, please refer to [suspendFunctionArgumentCaptor] for that.
167+
* This incompatibility is caused by the use of `typeOf<T>()` which is the way
168+
* to determine runtime nullability of T, but this function is yet incompatible with
169+
* suspend functions at compile time. That incompatibility has been declared since
170+
* Kotlin 1.6, and the promised proper support for suspend functions has not been
171+
* delivered ever since.
172+
* See [Kotlin issue KT-47562](https://youtrack.jetbrains.com/issue/KT-47562/Support-suspend-functional-types-in-typeOf)
173+
* for more details.
115174
*/
116175
inline fun <reified A : Any, reified B : Any, reified C : Any, reified D : Any, reified E : Any> argumentCaptor(
117-
a: KClass<A> = A::class,
118-
b: KClass<B> = B::class,
119-
c: KClass<C> = C::class,
120-
d: KClass<D> = D::class,
121-
e: KClass<E> = E::class
176+
@Suppress("unused") a: KClass<A> = A::class,
177+
@Suppress("unused") b: KClass<B> = B::class,
178+
@Suppress("unused") c: KClass<C> = C::class,
179+
@Suppress("unused") d: KClass<D> = D::class,
180+
@Suppress("unused") e: KClass<E> = E::class
122181
): ArgumentCaptorHolder5<KArgumentCaptor<A>, KArgumentCaptor<B>, KArgumentCaptor<C>, KArgumentCaptor<D>, KArgumentCaptor<E>> {
123182
return ArgumentCaptorHolder5(
124-
KArgumentCaptor(a),
125-
KArgumentCaptor(b),
126-
KArgumentCaptor(c),
127-
KArgumentCaptor(d),
128-
KArgumentCaptor(e)
183+
KArgumentCaptor(typeOf<A>()),
184+
KArgumentCaptor(typeOf<B>()),
185+
KArgumentCaptor(typeOf<C>()),
186+
KArgumentCaptor(typeOf<D>()),
187+
KArgumentCaptor(typeOf<E>())
129188
)
130189
}
131190

@@ -140,7 +199,7 @@ inline fun <reified T : Any> argumentCaptor(f: KArgumentCaptor<T>.() -> Unit): K
140199
* Creates a [KArgumentCaptor] for given nullable type.
141200
*/
142201
inline fun <reified T : Any> nullableArgumentCaptor(): KArgumentCaptor<T?> {
143-
return KArgumentCaptor(T::class)
202+
return KArgumentCaptor(typeOf<T>())
144203
}
145204

146205
/**
@@ -157,58 +216,61 @@ inline fun <reified T : Any> capture(captor: ArgumentCaptor<T>): T {
157216
return captor.capture() ?: createInstance()
158217
}
159218

160-
class KArgumentCaptor<out T : Any?> (
161-
private val tClass: KClass<*>
219+
class KArgumentCaptor<out T : Any?>(
220+
private val clazz: KClass<*>,
221+
private val isMarkedNullable: Boolean = false
162222
) {
223+
constructor(kType: KType):this(
224+
kType.classifier as KClass<*>,
225+
kType.isMarkedNullable
226+
)
227+
163228
private val captor: ArgumentCaptor<Any?> =
164-
if (tClass.isValue) {
165-
val boxImpl =
166-
tClass.java.declaredMethods
167-
.single { it.name == "box-impl" && it.parameterCount == 1 }
168-
boxImpl.parameters[0].type // is the boxed type of the value type
229+
if (clazz.isValue && !isMarkedNullable) {
230+
clazz.valueClassInnerClass()
169231
} else {
170-
tClass.java
232+
clazz
171233
}.let {
172-
ArgumentCaptor.forClass(it)
234+
ArgumentCaptor.forClass(it.java)
173235
}
174236

175237
/**
176238
* The first captured value of the argument.
177239
* @throws IndexOutOfBoundsException if the value is not available.
178240
*/
179241
val firstValue: T
180-
get() = toKotlinType(captor.firstValue)
242+
get() = captor.firstValue.toKotlinType(clazz)
181243

182244
/**
183245
* The second captured value of the argument.
184246
* @throws IndexOutOfBoundsException if the value is not available.
185247
*/
186248
val secondValue: T
187-
get() = toKotlinType(captor.secondValue)
249+
get() = captor.secondValue.toKotlinType(clazz)
188250

189251
/**
190252
* The third captured value of the argument.
191253
* @throws IndexOutOfBoundsException if the value is not available.
192254
*/
193255
val thirdValue: T
194-
get() = toKotlinType(captor.thirdValue)
256+
get() = captor.thirdValue.toKotlinType(clazz)
195257

196258
/**
197259
* The last captured value of the argument.
198260
* @throws IndexOutOfBoundsException if the value is not available.
199261
*/
200262
val lastValue: T
201-
get() = toKotlinType(captor.lastValue)
263+
get() = captor.lastValue.toKotlinType(clazz)
202264

203265
/**
204266
* The *only* captured value of the argument,
205267
* or throws an exception if no value or more than one value was captured.
206268
*/
207269
val singleValue: T
208-
get() = toKotlinType(captor.singleValue)
270+
get() = captor.singleValue.toKotlinType(clazz)
209271

210272
val allValues: List<T>
211-
get() = captor.allValues.map(::toKotlinType)
273+
get() = captor.allValues.map { it.toKotlinType(clazz) }
212274

213275
@Suppress("UNCHECKED_CAST")
214276
fun capture(): T {
@@ -219,28 +281,14 @@ class KArgumentCaptor<out T : Any?> (
219281
// In Java, `captor.capture` returns null and so the method is called with `[null]`
220282
// In Kotlin, we have to create `[null]` explicitly.
221283
// This code-path is applied for non-vararg array arguments as well, but it seems to work fine.
222-
return captor.capture() as T ?: if (tClass.java.isArray) {
284+
return captor.capture().toKotlinType(clazz) ?: if (clazz.java.isArray) {
223285
singleElementArray()
224286
} else {
225-
createInstance(tClass)
287+
createInstance(clazz)
226288
} as T
227289
}
228290

229-
private fun singleElementArray(): Any? = Array.newInstance(tClass.java.componentType, 1)
230-
231-
@Suppress("UNCHECKED_CAST")
232-
private fun toKotlinType(rawCapturedValue: Any?) : T {
233-
return if(tClass.isValue) {
234-
rawCapturedValue
235-
?.let {
236-
val boxImpl =
237-
tClass.java.declaredMethods.single { it.name == "box-impl" && it.parameterCount == 1 }
238-
boxImpl.invoke(null, it)
239-
} as T
240-
} else {
241-
rawCapturedValue as T
242-
}
243-
}
291+
private fun singleElementArray(): Any? = Array.newInstance(clazz.java.componentType, 1)
244292
}
245293

246294
val <T> ArgumentCaptor<T>.firstValue: T

0 commit comments

Comments
 (0)