Skip to content

Commit 3b5c40a

Browse files
authored
Merge pull request #102 from nhaarman/release-0.10.0
Release 0.10.0
2 parents 24108bd + 6af0904 commit 3b5c40a

File tree

5 files changed

+131
-17
lines changed

5 files changed

+131
-17
lines changed

mockito-kotlin/build.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ test.dependsOn testInlineMockito
4545
dependencies {
4646
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
4747
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
48-
compile "org.mockito:mockito-core:2.2.6"
48+
compile "org.mockito:mockito-core:2.2.9"
4949

5050
/* Tests */
5151
testCompile "junit:junit:4.12"
@@ -62,4 +62,4 @@ dokka {
6262
suffix = "#L"
6363
}
6464
}
65-
javadoc.dependsOn dokka
65+
javadoc.dependsOn dokka

mockito-kotlin/src/main/kotlin/com/nhaarman/mockito_kotlin/ArgumentCaptor.kt

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -29,29 +29,31 @@ import org.mockito.ArgumentCaptor
2929
import kotlin.reflect.KClass
3030

3131
inline fun <reified T : Any> argumentCaptor(): KArgumentCaptor<T> = KArgumentCaptor(ArgumentCaptor.forClass(T::class.java), T::class)
32+
inline fun <reified T : Any> nullableArgumentCaptor(): KArgumentCaptor<T?> = KArgumentCaptor(ArgumentCaptor.forClass(T::class.java), T::class)
3233

3334
inline fun <reified T : Any> capture(captor: ArgumentCaptor<T>): T = captor.capture() ?: createInstance<T>()
3435

3536
@Deprecated("Use captor.capture() instead.", ReplaceWith("captor.capture()"), DeprecationLevel.ERROR)
3637
inline fun <reified T : Any> capture(captor: KArgumentCaptor<T>): T = captor.capture()
3738

38-
class KArgumentCaptor<out T : Any>(private val captor: ArgumentCaptor<T>, private val tClass: KClass<T>) {
39+
class KArgumentCaptor<out T : Any?>(private val captor: ArgumentCaptor<T>, private val tClass: KClass<*>) {
3940

4041
val value: T
4142
get() = captor.value
4243

4344
val allValues: List<T>
4445
get() = captor.allValues
4546

46-
fun capture(): T = captor.capture() ?: createInstance(tClass)
47+
@Suppress("UNCHECKED_CAST")
48+
fun capture(): T = captor.capture() ?: createInstance(tClass) as T
4749
}
4850

4951
/**
5052
* This method is deprecated because its behavior differs from the Java behavior.
5153
* Instead, use [argumentCaptor] in the traditional way, or use one of
5254
* [argThat], [argForWhich] or [check].
5355
*/
54-
@Deprecated("Use argumentCaptor() or argThat() instead.", ReplaceWith("check(consumer)"), DeprecationLevel.ERROR)
56+
@Deprecated("Use argumentCaptor(), argThat() or check() instead.", ReplaceWith("check(consumer)"), DeprecationLevel.ERROR)
5557
inline fun <reified T : Any> capture(noinline consumer: (T) -> Unit): T {
5658
var times = 0
5759
return argThat { if (++times == 1) consumer.invoke(this); true }

mockito-kotlin/src/main/kotlin/com/nhaarman/mockito_kotlin/CreateInstance.kt

Lines changed: 18 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,10 +34,7 @@ import java.lang.reflect.InvocationTargetException
3434
import java.lang.reflect.Modifier
3535
import java.lang.reflect.ParameterizedType
3636
import java.lang.reflect.Type
37-
import kotlin.reflect.KClass
38-
import kotlin.reflect.KFunction
39-
import kotlin.reflect.KType
40-
import kotlin.reflect.defaultType
37+
import kotlin.reflect.*
4138
import kotlin.reflect.jvm.isAccessible
4239
import kotlin.reflect.jvm.javaType
4340
import kotlin.reflect.jvm.jvmName
@@ -84,15 +81,29 @@ fun <T : Any> createInstance(kClass: KClass<T>): T {
8481
*/
8582
private fun <T : Any> KClass<T>.easiestConstructor(): KFunction<T> {
8683
return constructors
87-
.sortedBy { it.parameters.size }
84+
.sortedBy { it.parameters.withoutOptionalParameters().size }
85+
.withoutParametersOfType(this.defaultType)
8886
.withoutArrayParameters()
89-
.firstOrNull() ?: constructors.sortedBy { it.parameters.size }.first()
87+
.firstOrNull() ?: constructors.sortedBy { it.parameters.withoutOptionalParameters().size }
88+
.withoutParametersOfType(this.defaultType)
89+
.first()
9090
}
9191

9292
private fun <T> List<KFunction<T>>.withoutArrayParameters() = filter {
9393
it.parameters.filter { parameter -> parameter.type.toString().toLowerCase().contains("array") }.isEmpty()
9494
}
9595

96+
/**
97+
* Filters out functions with the given type.
98+
* This is especially useful to avoid infinite loops where constructors
99+
* accepting a parameter of their own type, e.g. 'copy constructors'.
100+
*/
101+
private fun <T : Any> List<KFunction<T>>.withoutParametersOfType(type: KType) = filter {
102+
it.parameters.filter { it.type == type }.isEmpty()
103+
}
104+
105+
private fun List<KParameter>.withoutOptionalParameters() = filterNot { it.isOptional }
106+
96107
@Suppress("SENSELESS_COMPARISON")
97108
private fun KClass<*>.hasObjectInstance() = objectInstance != null
98109

@@ -154,7 +165,7 @@ private fun <T : Any> KClass<T>.toClassObject(): T {
154165
private fun <T : Any> KFunction<T>.newInstance(): T {
155166
try {
156167
isAccessible = true
157-
return callBy(parameters.associate {
168+
return callBy(parameters.withoutOptionalParameters().associate {
158169
it to it.type.createNullableInstance<T>()
159170
})
160171
} catch(e: InvocationTargetException) {

mockito-kotlin/src/test/kotlin/ArgumentCaptorTest.kt

Lines changed: 45 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,12 @@
11
import com.nhaarman.expect.expect
2-
import com.nhaarman.mockito_kotlin.argumentCaptor
3-
import com.nhaarman.mockito_kotlin.mock
4-
import com.nhaarman.mockito_kotlin.times
5-
import com.nhaarman.mockito_kotlin.verify
2+
import com.nhaarman.mockito_kotlin.*
63
import org.junit.Test
74
import java.util.*
85

96
class ArgumentCaptorTest {
107

118
@Test
12-
fun explicitCaptor() {
9+
fun argumentCaptor_withSingleValue() {
1310
/* Given */
1411
val date: Date = mock()
1512

@@ -22,6 +19,34 @@ class ArgumentCaptorTest {
2219
expect(captor.value).toBe(5L)
2320
}
2421

22+
@Test
23+
fun argumentCaptor_withNullValue_usingNonNullable() {
24+
/* Given */
25+
val m: Methods = mock()
26+
27+
/* When */
28+
m.nullableString(null)
29+
30+
/* Then */
31+
val captor = argumentCaptor<String>()
32+
verify(m).nullableString(captor.capture())
33+
expect(captor.value).toBeNull()
34+
}
35+
36+
@Test
37+
fun argumentCaptor_withNullValue_usingNullable() {
38+
/* Given */
39+
val m: Methods = mock()
40+
41+
/* When */
42+
m.nullableString(null)
43+
44+
/* Then */
45+
val captor = nullableArgumentCaptor<String>()
46+
verify(m).nullableString(captor.capture())
47+
expect(captor.value).toBeNull()
48+
}
49+
2550
@Test
2651
fun argumentCaptor_multipleValues() {
2752
/* Given */
@@ -36,4 +61,19 @@ class ArgumentCaptorTest {
3661
verify(date, times(2)).time = captor.capture()
3762
expect(captor.allValues).toBe(listOf(5, 7))
3863
}
64+
65+
@Test
66+
fun argumentCaptor_multipleValuesIncludingNull() {
67+
/* Given */
68+
val m: Methods = mock()
69+
70+
/* When */
71+
m.nullableString("test")
72+
m.nullableString(null)
73+
74+
/* Then */
75+
val captor = nullableArgumentCaptor<String>()
76+
verify(m, times(2)).nullableString(captor.capture())
77+
expect(captor.allValues).toBe(listOf("test", null))
78+
}
3979
}

mockito-kotlin/src/test/kotlin/CreateInstanceTest.kt

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,46 @@ class CreateInstanceTest {
436436
expect(result).toNotBeNull()
437437
}
438438

439+
/**
440+
* Bug: When the copy constructor is selected, we end up with an infinite
441+
* loop. Instead, we want to select a constructor that doesn't
442+
* take in a parameter with the same type as the one we are building.
443+
*
444+
* GIVEN a class with a copy constructor (in the case of the bug, the copy
445+
* constructor has to be selected, so it must have fewer parameters
446+
* than all other constructors)
447+
* WHEN we make an instance of the given class
448+
* THEN we expect that the new, non-null instance will be created without
449+
* an exception
450+
*/
451+
@Test
452+
fun copyConstructorDoesNotCauseException() {
453+
/* When */
454+
val result = createInstance(WithCopyConstructor::class)
455+
456+
/* Then */
457+
expect(result).toNotBeNull()
458+
}
459+
460+
@Test
461+
fun optionalParametersAreSkippedWhenSorting() {
462+
/* When */
463+
val result = createInstance(WithDefaultParameters::class)
464+
465+
/* Then */
466+
expect(result).toNotBeNull()
467+
}
468+
469+
@Test
470+
fun defaultValuesAreUsedWithOptionalParameters() {
471+
/* When */
472+
val result = createInstance(WithDefaultParameters::class)
473+
474+
/* Then */
475+
expect(result.first).toBe(1)
476+
expect(result.second).toBe(2)
477+
}
478+
439479
private class PrivateClass private constructor(val data: String)
440480

441481
class ClosedClass
@@ -475,5 +515,26 @@ class CreateInstanceTest {
475515
constructor(c: ForbiddenConstructor) : this()
476516
}
477517

518+
/**
519+
* Bug: When the copy constructor is selected, then create instance gets
520+
* into an infinite loop. We should never use the copy constructor in
521+
* createInstance.
522+
*/
523+
data class WithCopyConstructor private constructor(val x: String,
524+
val y: String) {
525+
constructor(other: WithCopyConstructor) : this(other.x, other.y)
526+
}
527+
528+
/**
529+
* A class that uses default parameters, but with a constructor without parameters that fails.
530+
* This is to make sure default parameters are not counted when sorting by parameter size.
531+
*/
532+
class WithDefaultParameters constructor(val first: Int = 1, val second: Int = 2) {
533+
534+
constructor(first: Int) : this() {
535+
error("Should not be called")
536+
}
537+
}
538+
478539
enum class MyEnum { VALUE, ANOTHER_VALUE }
479540
}

0 commit comments

Comments
 (0)