Skip to content

Commit cddcf7f

Browse files
committed
Introduce instance creators.
These are functions `() -> T` that are invoked when an instance of `T` is needed, instead of manually trying to figure out which constructor to use. Fixes #20.
1 parent 4cbd876 commit cddcf7f

File tree

5 files changed

+215
-12
lines changed

5 files changed

+215
-12
lines changed

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

Lines changed: 28 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ import org.mockito.Answers
2929
import org.mockito.internal.creation.MockSettingsImpl
3030
import org.mockito.internal.creation.bytebuddy.MockMethodInterceptor
3131
import org.mockito.internal.util.MockUtil
32+
import java.lang.reflect.InvocationTargetException
3233
import java.lang.reflect.Modifier
3334
import java.lang.reflect.ParameterizedType
3435
import java.lang.reflect.Type
@@ -49,16 +50,18 @@ inline fun <reified T> createArrayInstance() = arrayOf<T>()
4950

5051
inline fun <reified T : Any> createInstance() = createInstance(T::class)
5152

53+
@Suppress("UNCHECKED_CAST")
5254
fun <T : Any> createInstance(kClass: KClass<T>): T {
53-
return when {
54-
kClass.hasObjectInstance() -> kClass.objectInstance!!
55-
kClass.isMockable() -> kClass.java.uncheckedMock()
56-
kClass.isPrimitive() -> kClass.toDefaultPrimitiveValue()
57-
kClass.isEnum() -> kClass.java.enumConstants.first()
58-
kClass.isArray() -> kClass.toArrayInstance()
55+
return MockitoKotlin.instanceCreator(kClass)?.invoke() as T? ?:
56+
when {
57+
kClass.hasObjectInstance() -> kClass.objectInstance!!
58+
kClass.isMockable() -> kClass.java.uncheckedMock()
59+
kClass.isPrimitive() -> kClass.toDefaultPrimitiveValue()
60+
kClass.isEnum() -> kClass.java.enumConstants.first()
61+
kClass.isArray() -> kClass.toArrayInstance()
5962
kClass.isClassObject() -> kClass.toClassObject()
60-
else -> kClass.easiestConstructor().newInstance()
61-
}
63+
else -> kClass.easiestConstructor().newInstance()
64+
}
6265
}
6366

6467
/**
@@ -134,10 +137,23 @@ private fun <T : Any> KClass<T>.toClassObject(): T {
134137
}
135138

136139
private fun <T : Any> KFunction<T>.newInstance(): T {
137-
isAccessible = true
138-
return callBy(parameters.associate {
139-
it to it.type.createNullableInstance<T>()
140-
})
140+
try {
141+
isAccessible = true
142+
return callBy(parameters.associate {
143+
it to it.type.createNullableInstance<T>()
144+
})
145+
} catch(e: InvocationTargetException) {
146+
throw MockitoKotlinException(
147+
"""
148+
149+
Could not create an instance of class ${this.returnType}, because of an error with the following message:
150+
151+
"${e.cause?.message}"
152+
153+
Try registering an instance creator yourself, using MockitoKotlin.registerInstanceCreator<${this.returnType}> {...}.""",
154+
e.cause
155+
)
156+
}
141157
}
142158

143159
@Suppress("UNCHECKED_CAST")
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2016 Niek Haarman
5+
* Copyright (c) 2007 Mockito contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
26+
package com.nhaarman.mockito_kotlin
27+
28+
import java.util.*
29+
import kotlin.reflect.KClass
30+
31+
class MockitoKotlin {
32+
33+
companion object {
34+
35+
/**
36+
* Maps KClasses to functions that can create an instance of that KClass.
37+
*/
38+
private val creators: MutableMap<KClass<*>, () -> Any> = HashMap()
39+
40+
/**
41+
* Registers a function to be called when an instance of T is necessary.
42+
*/
43+
inline fun <reified T : Any> registerInstanceCreator(noinline creator: () -> T) = registerInstanceCreator(T::class, creator)
44+
45+
/**
46+
* Registers a function to be called when an instance of T is necessary.
47+
*/
48+
fun <T : Any> registerInstanceCreator(kClass: KClass<T>, creator: () -> T) = creators.put(kClass, creator)
49+
50+
/**
51+
* Unregisters an instance creator.
52+
*/
53+
inline fun <reified T : Any> unregisterInstanceCreator() = unregisterInstanceCreator(T::class)
54+
55+
/**
56+
* Unregisters an instance creator.
57+
*/
58+
fun <T : Any> unregisterInstanceCreator(kClass: KClass<T>) = creators.remove(kClass)
59+
60+
/**
61+
* Clears al instance creators.
62+
*/
63+
fun resetInstanceCreators() = creators.clear()
64+
65+
internal fun <T : Any> instanceCreator(kClass: KClass<T>): (() -> Any)? {
66+
return creators[kClass]
67+
}
68+
}
69+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2016 Niek Haarman
5+
* Copyright (c) 2007 Mockito contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
26+
package com.nhaarman.mockito_kotlin
27+
28+
class MockitoKotlinException(message: String?, cause: Throwable?) : RuntimeException(message, cause)

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

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424
*/
2525

2626
import com.nhaarman.expect.expect
27+
import com.nhaarman.expect.expectErrorWithMessage
28+
import com.nhaarman.mockito_kotlin.MockitoKotlin
2729
import com.nhaarman.mockito_kotlin.createInstance
2830
import org.junit.Test
2931
import java.util.*
@@ -403,6 +405,25 @@ class CreateInstanceTest {
403405
expect(result).toBe(UUID(0, 0))
404406
}
405407

408+
@Test
409+
fun registeredInstanceCreator() {
410+
/* Given */
411+
MockitoKotlin.registerInstanceCreator { ForbiddenConstructor(2) }
412+
413+
/* When */
414+
val result = createInstance<ForbiddenConstructor>()
415+
416+
/* Then */
417+
expect(result).toNotBeNull()
418+
}
419+
420+
@Test
421+
fun failedConstructor_throwsDescriptiveError() {
422+
expectErrorWithMessage("Could not create an instance of class") on {
423+
createInstance<ForbiddenConstructor>()
424+
}
425+
}
426+
406427
private class PrivateClass private constructor(val data: String)
407428

408429
class ClosedClass
@@ -428,5 +449,15 @@ class CreateInstanceTest {
428449
class ParameterizedClass<T>(val t: T)
429450
class NullableParameterClass(val s: String?)
430451

452+
class ForbiddenConstructor {
453+
454+
constructor() {
455+
throw AssertionError("Forbidden.")
456+
}
457+
458+
constructor(value: Int) {
459+
}
460+
}
461+
431462
enum class MyEnum { VALUE, ANOTHER_VALUE }
432463
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2016 Niek Haarman
5+
* Copyright (c) 2007 Mockito contributors
6+
*
7+
* Permission is hereby granted, free of charge, to any person obtaining a copy
8+
* of this software and associated documentation files (the "Software"), to deal
9+
* in the Software without restriction, including without limitation the rights
10+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11+
* copies of the Software, and to permit persons to whom the Software is
12+
* furnished to do so, subject to the following conditions:
13+
*
14+
* The above copyright notice and this permission notice shall be included in
15+
* all copies or substantial portions of the Software.
16+
*
17+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
23+
* THE SOFTWARE.
24+
*/
25+
26+
import com.nhaarman.expect.expect
27+
import com.nhaarman.mockito_kotlin.MockitoKotlin
28+
import com.nhaarman.mockito_kotlin.createInstance
29+
import org.junit.Test
30+
31+
class MockitoKotlinTest {
32+
33+
@Test
34+
fun register() {
35+
/* Given */
36+
val closed = Closed()
37+
MockitoKotlin.registerInstanceCreator { closed }
38+
39+
/* When */
40+
val result = createInstance<Closed>()
41+
42+
/* Then */
43+
expect(result).toBe(closed)
44+
}
45+
46+
@Test
47+
fun unregister() {
48+
/* Given */
49+
val closed = Closed()
50+
MockitoKotlin.registerInstanceCreator { closed }
51+
MockitoKotlin.unregisterInstanceCreator<Closed>()
52+
53+
/* When */
54+
val result = createInstance<Closed>()
55+
56+
/* Then */
57+
expect(result).toNotBeReferentially(closed)
58+
}
59+
}

0 commit comments

Comments
 (0)