Skip to content
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.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 49 additions & 25 deletions mockito-kotlin/src/main/kotlin/org/mockito/kotlin/ArgumentCaptor.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ import kotlin.reflect.KClass
* Creates a [KArgumentCaptor] for given type.
*/
inline fun <reified T : Any> argumentCaptor(): KArgumentCaptor<T> {
return KArgumentCaptor(ArgumentCaptor.forClass(T::class.java), T::class)
return KArgumentCaptor(T::class)
}

/**
Expand All @@ -45,8 +45,8 @@ inline fun <reified A : Any, reified B : Any> argumentCaptor(
b: KClass<B> = B::class
): Pair<KArgumentCaptor<A>, KArgumentCaptor<B>> {
return Pair(
KArgumentCaptor(ArgumentCaptor.forClass(a.java), a),
KArgumentCaptor(ArgumentCaptor.forClass(b.java), b)
KArgumentCaptor(a),
KArgumentCaptor(b)
)
}

Expand All @@ -59,9 +59,9 @@ inline fun <reified A : Any, reified B : Any, reified C : Any> argumentCaptor(
c: KClass<C> = C::class
): Triple<KArgumentCaptor<A>, KArgumentCaptor<B>, KArgumentCaptor<C>> {
return Triple(
KArgumentCaptor(ArgumentCaptor.forClass(a.java), a),
KArgumentCaptor(ArgumentCaptor.forClass(b.java), b),
KArgumentCaptor(ArgumentCaptor.forClass(c.java), c)
KArgumentCaptor(a),
KArgumentCaptor(b),
KArgumentCaptor(c)
)
}

Expand Down Expand Up @@ -103,10 +103,10 @@ inline fun <reified A : Any, reified B : Any, reified C : Any, reified D : Any>
d: KClass<D> = D::class
): ArgumentCaptorHolder4<KArgumentCaptor<A>, KArgumentCaptor<B>, KArgumentCaptor<C>, KArgumentCaptor<D>> {
return ArgumentCaptorHolder4(
KArgumentCaptor(ArgumentCaptor.forClass(a.java), a),
KArgumentCaptor(ArgumentCaptor.forClass(b.java), b),
KArgumentCaptor(ArgumentCaptor.forClass(c.java), c),
KArgumentCaptor(ArgumentCaptor.forClass(d.java), d)
KArgumentCaptor(a),
KArgumentCaptor(b),
KArgumentCaptor(c),
KArgumentCaptor(d)
)
}

Expand All @@ -121,11 +121,11 @@ inline fun <reified A : Any, reified B : Any, reified C : Any, reified D : Any,
e: KClass<E> = E::class
): ArgumentCaptorHolder5<KArgumentCaptor<A>, KArgumentCaptor<B>, KArgumentCaptor<C>, KArgumentCaptor<D>, KArgumentCaptor<E>> {
return ArgumentCaptorHolder5(
KArgumentCaptor(ArgumentCaptor.forClass(a.java), a),
KArgumentCaptor(ArgumentCaptor.forClass(b.java), b),
KArgumentCaptor(ArgumentCaptor.forClass(c.java), c),
KArgumentCaptor(ArgumentCaptor.forClass(d.java), d),
KArgumentCaptor(ArgumentCaptor.forClass(e.java), e)
KArgumentCaptor(a),
KArgumentCaptor(b),
KArgumentCaptor(c),
KArgumentCaptor(d),
KArgumentCaptor(e)
)
}

Expand All @@ -140,7 +140,7 @@ inline fun <reified T : Any> argumentCaptor(f: KArgumentCaptor<T>.() -> Unit): K
* Creates a [KArgumentCaptor] for given nullable type.
*/
inline fun <reified T : Any> nullableArgumentCaptor(): KArgumentCaptor<T?> {
return KArgumentCaptor(ArgumentCaptor.forClass(T::class.java), T::class)
return KArgumentCaptor(T::class)
}

/**
Expand All @@ -157,48 +157,58 @@ inline fun <reified T : Any> capture(captor: ArgumentCaptor<T>): T {
return captor.capture() ?: createInstance()
}

class KArgumentCaptor<out T : Any?>(
private val captor: ArgumentCaptor<T>,
class KArgumentCaptor<out T : Any?> (
private val tClass: KClass<*>
) {
private val captor: ArgumentCaptor<Any?> =
if (tClass.isValue) {
val boxImpl =
tClass.java.declaredMethods
.single { it.name == "box-impl" && it.parameterCount == 1 }
boxImpl.parameters[0].type // is the boxed type of the value type
} else {
tClass.java
}.let {
ArgumentCaptor.forClass(it)
}

/**
* The first captured value of the argument.
* @throws IndexOutOfBoundsException if the value is not available.
*/
val firstValue: T
get() = captor.firstValue
get() = toKotlinType(captor.firstValue)

/**
* The second captured value of the argument.
* @throws IndexOutOfBoundsException if the value is not available.
*/
val secondValue: T
get() = captor.secondValue
get() = toKotlinType(captor.secondValue)

/**
* The third captured value of the argument.
* @throws IndexOutOfBoundsException if the value is not available.
*/
val thirdValue: T
get() = captor.thirdValue
get() = toKotlinType(captor.thirdValue)

/**
* The last captured value of the argument.
* @throws IndexOutOfBoundsException if the value is not available.
*/
val lastValue: T
get() = captor.lastValue
get() = toKotlinType(captor.lastValue)

/**
* The *only* captured value of the argument,
* or throws an exception if no value or more than one value was captured.
*/
val singleValue: T
get() = captor.singleValue
get() = toKotlinType(captor.singleValue)

val allValues: List<T>
get() = captor.allValues
get() = captor.allValues.map(::toKotlinType)

@Suppress("UNCHECKED_CAST")
fun capture(): T {
Expand All @@ -209,14 +219,28 @@ class KArgumentCaptor<out T : Any?>(
// In Java, `captor.capture` returns null and so the method is called with `[null]`
// In Kotlin, we have to create `[null]` explicitly.
// This code-path is applied for non-vararg array arguments as well, but it seems to work fine.
return captor.capture() ?: if (tClass.java.isArray) {
return captor.capture() as T ?: if (tClass.java.isArray) {
singleElementArray()
} else {
createInstance(tClass)
} as T
}

private fun singleElementArray(): Any? = Array.newInstance(tClass.java.componentType, 1)

@Suppress("UNCHECKED_CAST")
private fun toKotlinType(rawCapturedValue: Any?) : T {
return if(tClass.isValue) {
rawCapturedValue
?.let {
val boxImpl =
tClass.java.declaredMethods.single { it.name == "box-impl" && it.parameterCount == 1 }
boxImpl.invoke(null, it)
} as T
} else {
rawCapturedValue as T
}
}
}

val <T> ArgumentCaptor<T>.firstValue: T
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,8 +41,15 @@ inline fun <reified T : Any> createInstance(): T {
}
}

@Suppress("UNCHECKED_CAST")
fun <T : Any> createInstance(@Suppress("UNUSED_PARAMETER") kClass: KClass<T>): T {
return castNull()
return if(kClass.isValue) {
val boxImpl =
kClass.java.declaredMethods.single { it.name == "box-impl" && it.parameterCount == 1 }
boxImpl.invoke(null, castNull()) as T
} else {
castNull()
}
}

/**
Expand Down
46 changes: 43 additions & 3 deletions tests/src/test/kotlin/test/ArgumentCaptorTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,6 @@ package test
import com.nhaarman.expect.expect
import com.nhaarman.expect.expectErrorWithMessage
import org.junit.Test
import org.mockito.ArgumentCaptor
import org.mockito.ArgumentMatchers
import org.mockito.ArgumentMatchers.anyLong
import org.mockito.kotlin.*
import java.util.*

Expand Down Expand Up @@ -342,4 +339,47 @@ class ArgumentCaptorTest : TestBase() {
verify(m).stringArray(captor.capture())
expect(captor.firstValue.toList()).toBe(listOf())
}

@Test
fun argumentCaptor_value_class() {
/* Given */
val m: Methods = mock()
val valueClass = ValueClass("Content")

/* When */
m.valueClass(valueClass)

/* Then */
val captor = argumentCaptor<ValueClass>()
verify(m).valueClass(captor.capture())
expect(captor.firstValue).toBe(valueClass)
}

@Test
fun argumentCaptor_value_class_withNullValue_usingNonNullable() {
/* Given */
val m: Methods = mock()

/* When */
m.nullableValueClass(null)

/* Then */
val captor = argumentCaptor<ValueClass>()
verify(m).nullableValueClass(captor.capture())
expect(captor.firstValue).toBeNull()
}

@Test
fun argumentCaptor_value_class_withNullValue_usingNullable() {
/* Given */
val m: Methods = mock()

/* When */
m.nullableValueClass(null)

/* Then */
val captor = nullableArgumentCaptor<ValueClass>()
verify(m).nullableValueClass(captor.capture())
expect(captor.firstValue).toBeNull()
}
}
3 changes: 2 additions & 1 deletion tests/src/test/kotlin/test/Classes.kt
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,8 @@ interface Methods {

fun nonDefaultReturnType(): ExtraInterface

fun valueClass(v: ValueClass?)
fun valueClass(v: ValueClass)
fun nullableValueClass(v: ValueClass?)
fun nestedValueClass(v: NestedValueClass)
}

Expand Down
8 changes: 4 additions & 4 deletions tests/src/test/kotlin/test/MatchersTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -349,16 +349,16 @@ class MatchersTest : TestBase() {
@Test
fun anyOrNull_forValueClass() {
mock<Methods>().apply {
valueClass(ValueClass("Content"))
verify(this).valueClass(anyOrNull())
nullableValueClass(ValueClass("Content"))
verify(this).nullableValueClass(anyOrNull())
}
}

@Test
fun anyOrNull_forValueClass_withNull() {
mock<Methods>().apply {
valueClass(null)
verify(this).valueClass(anyOrNull())
nullableValueClass(null)
verify(this).nullableValueClass(anyOrNull())
}
}

Expand Down