Skip to content

Commit 62cd94f

Browse files
committed
Merge pull request #38 from nhaarman/release-0.5.0
Release 0.5.0
2 parents 4cbd876 + aa49bde commit 62cd94f

File tree

6 files changed

+246
-12
lines changed

6 files changed

+246
-12
lines changed

README.md

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,23 @@ For generic arrays, use the `anyArray()` method:
6565
verify(myClass).setItems(anyArray())
6666
```
6767

68+
## Custom instance creators
69+
70+
There are some cases where Mockito-Kotlin cannot create an instance of a class.
71+
This can for instance be when a constructor has some specific preconditions
72+
for its parameters.
73+
You can _register_ `instance creators` to overcome this:
74+
75+
```kotlin
76+
MockitoKotlin.registerInstanceCreator<MyClass> { MyClass(5) }
77+
```
78+
79+
Whenever MockitoKotlin needs to create an instance of `MyClass`, this function is called,
80+
giving you ultimate control over how these instances are created.
81+
82+
These instance creators work on a per-file basis: for each of your test files
83+
you will need to register them again.
84+
6885
### Argument Matchers
6986

7087
Using higher-order functions, you can write very clear expectations about expected values.

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: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
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<*>, MutableList<Pair<String, () -> 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) {
49+
val element = Error().stackTrace[1]
50+
51+
creators.getOrPut(kClass) { ArrayList<Pair<String, () -> Any>>() }
52+
.add(element.toFileIdentifier() to creator)
53+
}
54+
55+
/**
56+
* Unregisters an instance creator.
57+
*/
58+
inline fun <reified T : Any> unregisterInstanceCreator() = unregisterInstanceCreator(T::class)
59+
60+
/**
61+
* Unregisters an instance creator.
62+
*/
63+
fun <T : Any> unregisterInstanceCreator(kClass: KClass<T>) = creators.remove(kClass)
64+
65+
/**
66+
* Clears al instance creators.
67+
*/
68+
fun resetInstanceCreators() = creators.clear()
69+
70+
internal fun <T : Any> instanceCreator(kClass: KClass<T>): (() -> Any)? {
71+
val stacktrace = Error().stackTrace.filterNot {
72+
it.className.contains("com.nhaarman.mockito_kotlin")
73+
}[0]
74+
75+
return creators[kClass]
76+
?.filter { it.first == stacktrace.toFileIdentifier() }
77+
?.firstOrNull()
78+
?.second
79+
}
80+
81+
private fun StackTraceElement.toFileIdentifier() = "$fileName$className"
82+
}
83+
}
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)