Skip to content

Commit

Permalink
Merge pull request #102 from nhaarman/release-0.10.0
Browse files Browse the repository at this point in the history
Release 0.10.0
  • Loading branch information
nhaarman committed Oct 26, 2016
2 parents 24108bd + 6af0904 commit 3b5c40a
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 17 deletions.
4 changes: 2 additions & 2 deletions mockito-kotlin/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ test.dependsOn testInlineMockito
dependencies {
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
compile "org.mockito:mockito-core:2.2.6"
compile "org.mockito:mockito-core:2.2.9"

/* Tests */
testCompile "junit:junit:4.12"
Expand All @@ -62,4 +62,4 @@ dokka {
suffix = "#L"
}
}
javadoc.dependsOn dokka
javadoc.dependsOn dokka
Original file line number Diff line number Diff line change
Expand Up @@ -29,29 +29,31 @@ import org.mockito.ArgumentCaptor
import kotlin.reflect.KClass

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

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

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

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

val value: T
get() = captor.value

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

fun capture(): T = captor.capture() ?: createInstance(tClass)
@Suppress("UNCHECKED_CAST")
fun capture(): T = captor.capture() ?: createInstance(tClass) as T
}

/**
* This method is deprecated because its behavior differs from the Java behavior.
* Instead, use [argumentCaptor] in the traditional way, or use one of
* [argThat], [argForWhich] or [check].
*/
@Deprecated("Use argumentCaptor() or argThat() instead.", ReplaceWith("check(consumer)"), DeprecationLevel.ERROR)
@Deprecated("Use argumentCaptor(), argThat() or check() instead.", ReplaceWith("check(consumer)"), DeprecationLevel.ERROR)
inline fun <reified T : Any> capture(noinline consumer: (T) -> Unit): T {
var times = 0
return argThat { if (++times == 1) consumer.invoke(this); true }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,7 @@ import java.lang.reflect.InvocationTargetException
import java.lang.reflect.Modifier
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import kotlin.reflect.KClass
import kotlin.reflect.KFunction
import kotlin.reflect.KType
import kotlin.reflect.defaultType
import kotlin.reflect.*
import kotlin.reflect.jvm.isAccessible
import kotlin.reflect.jvm.javaType
import kotlin.reflect.jvm.jvmName
Expand Down Expand Up @@ -84,15 +81,29 @@ fun <T : Any> createInstance(kClass: KClass<T>): T {
*/
private fun <T : Any> KClass<T>.easiestConstructor(): KFunction<T> {
return constructors
.sortedBy { it.parameters.size }
.sortedBy { it.parameters.withoutOptionalParameters().size }
.withoutParametersOfType(this.defaultType)
.withoutArrayParameters()
.firstOrNull() ?: constructors.sortedBy { it.parameters.size }.first()
.firstOrNull() ?: constructors.sortedBy { it.parameters.withoutOptionalParameters().size }
.withoutParametersOfType(this.defaultType)
.first()
}

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

/**
* Filters out functions with the given type.
* This is especially useful to avoid infinite loops where constructors
* accepting a parameter of their own type, e.g. 'copy constructors'.
*/
private fun <T : Any> List<KFunction<T>>.withoutParametersOfType(type: KType) = filter {
it.parameters.filter { it.type == type }.isEmpty()
}

private fun List<KParameter>.withoutOptionalParameters() = filterNot { it.isOptional }

@Suppress("SENSELESS_COMPARISON")
private fun KClass<*>.hasObjectInstance() = objectInstance != null

Expand Down Expand Up @@ -154,7 +165,7 @@ private fun <T : Any> KClass<T>.toClassObject(): T {
private fun <T : Any> KFunction<T>.newInstance(): T {
try {
isAccessible = true
return callBy(parameters.associate {
return callBy(parameters.withoutOptionalParameters().associate {
it to it.type.createNullableInstance<T>()
})
} catch(e: InvocationTargetException) {
Expand Down
50 changes: 45 additions & 5 deletions mockito-kotlin/src/test/kotlin/ArgumentCaptorTest.kt
Original file line number Diff line number Diff line change
@@ -1,15 +1,12 @@
import com.nhaarman.expect.expect
import com.nhaarman.mockito_kotlin.argumentCaptor
import com.nhaarman.mockito_kotlin.mock
import com.nhaarman.mockito_kotlin.times
import com.nhaarman.mockito_kotlin.verify
import com.nhaarman.mockito_kotlin.*
import org.junit.Test
import java.util.*

class ArgumentCaptorTest {

@Test
fun explicitCaptor() {
fun argumentCaptor_withSingleValue() {
/* Given */
val date: Date = mock()

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

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

/* When */
m.nullableString(null)

/* Then */
val captor = argumentCaptor<String>()
verify(m).nullableString(captor.capture())
expect(captor.value).toBeNull()
}

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

/* When */
m.nullableString(null)

/* Then */
val captor = nullableArgumentCaptor<String>()
verify(m).nullableString(captor.capture())
expect(captor.value).toBeNull()
}

@Test
fun argumentCaptor_multipleValues() {
/* Given */
Expand All @@ -36,4 +61,19 @@ class ArgumentCaptorTest {
verify(date, times(2)).time = captor.capture()
expect(captor.allValues).toBe(listOf(5, 7))
}

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

/* When */
m.nullableString("test")
m.nullableString(null)

/* Then */
val captor = nullableArgumentCaptor<String>()
verify(m, times(2)).nullableString(captor.capture())
expect(captor.allValues).toBe(listOf("test", null))
}
}
61 changes: 61 additions & 0 deletions mockito-kotlin/src/test/kotlin/CreateInstanceTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,46 @@ class CreateInstanceTest {
expect(result).toNotBeNull()
}

/**
* Bug: When the copy constructor is selected, we end up with an infinite
* loop. Instead, we want to select a constructor that doesn't
* take in a parameter with the same type as the one we are building.
*
* GIVEN a class with a copy constructor (in the case of the bug, the copy
* constructor has to be selected, so it must have fewer parameters
* than all other constructors)
* WHEN we make an instance of the given class
* THEN we expect that the new, non-null instance will be created without
* an exception
*/
@Test
fun copyConstructorDoesNotCauseException() {
/* When */
val result = createInstance(WithCopyConstructor::class)

/* Then */
expect(result).toNotBeNull()
}

@Test
fun optionalParametersAreSkippedWhenSorting() {
/* When */
val result = createInstance(WithDefaultParameters::class)

/* Then */
expect(result).toNotBeNull()
}

@Test
fun defaultValuesAreUsedWithOptionalParameters() {
/* When */
val result = createInstance(WithDefaultParameters::class)

/* Then */
expect(result.first).toBe(1)
expect(result.second).toBe(2)
}

private class PrivateClass private constructor(val data: String)

class ClosedClass
Expand Down Expand Up @@ -475,5 +515,26 @@ class CreateInstanceTest {
constructor(c: ForbiddenConstructor) : this()
}

/**
* Bug: When the copy constructor is selected, then create instance gets
* into an infinite loop. We should never use the copy constructor in
* createInstance.
*/
data class WithCopyConstructor private constructor(val x: String,
val y: String) {
constructor(other: WithCopyConstructor) : this(other.x, other.y)
}

/**
* A class that uses default parameters, but with a constructor without parameters that fails.
* This is to make sure default parameters are not counted when sorting by parameter size.
*/
class WithDefaultParameters constructor(val first: Int = 1, val second: Int = 2) {

constructor(first: Int) : this() {
error("Should not be called")
}
}

enum class MyEnum { VALUE, ANOTHER_VALUE }
}

0 comments on commit 3b5c40a

Please sign in to comment.