Skip to content

Commit 6965414

Browse files
desilvainhaarman
authored andcommitted
Fix compatibility with mock-maker-inline for mocking final classes
This includes a check for whether the resource file containing 'mock-maker-inline' is present to determine whether or not a class is mockable. It also adds an instanceof check before trying to cast an unchecked mock to a MockAccess object.
1 parent ea6e64c commit 6965414

File tree

6 files changed

+202
-57
lines changed

6 files changed

+202
-57
lines changed

mockito-kotlin/build.gradle

Lines changed: 31 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1,73 +1,50 @@
11
apply plugin: 'kotlin'
2+
apply from: './publishing.gradle'
23

34
buildscript {
4-
ext.kotlin_version = '1.0.4'
5+
ext.kotlin_version = '1.0.4'
56

6-
repositories {
7-
mavenCentral()
8-
}
7+
repositories {
8+
mavenCentral()
9+
}
910

10-
dependencies {
11-
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
12-
}
11+
dependencies {
12+
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
13+
}
1314
}
1415

1516
repositories {
16-
mavenCentral()
17-
jcenter()
18-
}
19-
20-
dependencies {
21-
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
22-
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
23-
compile "org.mockito:mockito-core:2.1.0"
24-
25-
/* Tests */
26-
testCompile "junit:junit:4.12"
27-
testCompile "com.nhaarman:expect.kt:0.6.0"
17+
mavenCentral()
18+
jcenter()
2819
}
2920

30-
publishing {
31-
publications {
32-
MyPublication(MavenPublication) {
33-
from components.java
34-
artifact javadocJar
35-
artifact sourcesJar
36-
37-
groupId 'com.nhaarman'
38-
artifactId 'mockito-kotlin'
39-
version rootProject.ext.versionName
21+
sourceSets {
22+
testInlineMockito {
23+
compileClasspath += main.output + test.output
24+
runtimeClasspath += main.output + test.output
4025
}
41-
}
4226
}
4327

44-
bintray {
45-
user = hasProperty('bintray_user') ? bintray_user : System.getenv('BINTRAY_USER')
46-
key = hasProperty('bintray_key') ? bintray_key : System.getenv('BINTRAY_KEY')
47-
publications = ['MyPublication']
28+
configurations {
29+
testInlineMockitoCompile.extendsFrom testCompile
30+
testInlineMockitoRuntime.extendsFrom testRuntime
31+
}
4832

49-
pkg {
50-
repo = 'maven'
51-
name = "Mockito-Kotlin"
52-
desc = "Using Mockito with Kotlin"
33+
// define custom test task for running integration tests
34+
task testInlineMockito(type: Test) {
35+
testClassesDir = sourceSets.testInlineMockito.output.classesDir
36+
classpath = sourceSets.testInlineMockito.runtimeClasspath
37+
}
5338

54-
licenses = ['MIT']
55-
vcsUrl = 'https://github.com/bintray/gradle-bintray-plugin.git'
39+
test.dependsOn testInlineMockito
5640

57-
version {
58-
name = rootProject.ext.versionName
59-
desc = 'Using Mockito with Kotlin'
60-
vcsTag = rootProject.ext.versionName
61-
}
62-
}
63-
}
6441

65-
task javadocJar(type: Jar, dependsOn: javadoc) {
66-
classifier = 'javadoc'
67-
from 'build/docs/javadoc'
68-
}
42+
dependencies {
43+
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
44+
compile "org.jetbrains.kotlin:kotlin-reflect:$kotlin_version"
45+
compile "org.mockito:mockito-core:2.1.0"
6946

70-
task sourcesJar(type: Jar) {
71-
from sourceSets.main.allSource
72-
classifier = 'sources'
47+
/* Tests */
48+
testCompile "junit:junit:4.12"
49+
testCompile "com.nhaarman:expect.kt:0.6.0"
7350
}

mockito-kotlin/publishing.gradle

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
publishing {
2+
publications {
3+
MyPublication(MavenPublication) {
4+
from components.java
5+
artifact javadocJar
6+
artifact sourcesJar
7+
8+
groupId 'com.nhaarman'
9+
artifactId 'mockito-kotlin'
10+
version rootProject.ext.versionName
11+
}
12+
}
13+
}
14+
15+
bintray {
16+
user = hasProperty('bintray_user') ? bintray_user : System.getenv('BINTRAY_USER')
17+
key = hasProperty('bintray_key') ? bintray_key : System.getenv('BINTRAY_KEY')
18+
publications = ['MyPublication']
19+
20+
pkg {
21+
repo = 'maven'
22+
name = "Mockito-Kotlin"
23+
desc = "Using Mockito with Kotlin"
24+
25+
licenses = ['MIT']
26+
vcsUrl = 'https://github.com/bintray/gradle-bintray-plugin.git'
27+
28+
version {
29+
name = rootProject.ext.versionName
30+
desc = 'Using Mockito with Kotlin'
31+
vcsTag = rootProject.ext.versionName
32+
}
33+
}
34+
}
35+
36+
task javadocJar(type: Jar, dependsOn: javadoc) {
37+
classifier = 'javadoc'
38+
from 'build/docs/javadoc'
39+
}
40+
41+
task sourcesJar(type: Jar) {
42+
from sourceSets.main.allSource
43+
classifier = 'sources'
44+
}

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

Lines changed: 19 additions & 3 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.MockAccess
3131
import org.mockito.internal.util.MockUtil
32+
import java.io.File
3233
import java.lang.reflect.InvocationTargetException
3334
import java.lang.reflect.Modifier
3435
import java.lang.reflect.ParameterizedType
@@ -46,6 +47,18 @@ import kotlin.reflect.jvm.jvmName
4647
* classes to avoid NPE's when using Mockito with Kotlin.
4748
*/
4849

50+
/**
51+
* Checks whether the resource file to enable mocking of final classes is present.
52+
*/
53+
private var mockMakerInlineEnabled: Boolean? = null
54+
private fun mockMakerInlineEnabled(jClass: Class<out Any>): Boolean {
55+
return mockMakerInlineEnabled ?:
56+
jClass.getResource("mockito-extensions/org.mockito.plugins.MockMaker")?.let {
57+
mockMakerInlineEnabled = File(it.file).readLines().filter { it == "mock-maker-inline" }.isNotEmpty()
58+
mockMakerInlineEnabled
59+
} ?: false
60+
}
61+
4962
inline fun <reified T> createArrayInstance() = arrayOf<T>()
5063

5164
inline fun <reified T : Any> createInstance() = createInstance(T::class)
@@ -81,7 +94,10 @@ private fun <T> List<KFunction<T>>.withoutArrayParameters() = filter {
8194
@Suppress("SENSELESS_COMPARISON")
8295
private fun KClass<*>.hasObjectInstance() = objectInstance != null
8396

84-
private fun KClass<*>.isMockable() = !Modifier.isFinal(java.modifiers)
97+
private fun KClass<*>.isMockable(): Boolean {
98+
return !Modifier.isFinal(java.modifiers) || mockMakerInlineEnabled(java)
99+
}
100+
85101
private fun KClass<*>.isEnum() = java.isEnum
86102
private fun KClass<*>.isArray() = java.isArray
87103
private fun KClass<*>.isClassObject() = jvmName.equals("java.lang.Class")
@@ -168,10 +184,10 @@ private fun <T : Any> KType.createNullableInstance(): T? {
168184
* Creates a mock instance of given class, without modifying or checking any internal Mockito state.
169185
*/
170186
@Suppress("UNCHECKED_CAST")
171-
private fun <T> Class<T>.uncheckedMock(): T {
187+
fun <T> Class<T>.uncheckedMock(): T {
172188
val impl = MockSettingsImpl<T>().defaultAnswer(Answers.RETURNS_DEFAULTS) as MockSettingsImpl<T>
173189
val creationSettings = impl.confirm(this)
174190
return MockUtil.createMock(creationSettings).apply {
175-
(this as MockAccess).mockitoInterceptor = null
191+
(this as? MockAccess)?.mockitoInterceptor = null
176192
}
177193
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,7 @@ class MockitoTest {
132132
}
133133
}
134134

135+
/** https://github.com/nhaarman/mockito-kotlin/issues/27 */
135136
@Test
136137
fun anyThrowableWithSingleThrowableConstructor() {
137138
mock<Methods>().apply {
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
/*
2+
* The MIT License
3+
*
4+
* Copyright (c) 2016 Ian J. De Silva
5+
*
6+
* Permission is hereby granted, free of charge, to any person obtaining a copy
7+
* of this software and associated documentation files (the "Software"), to deal
8+
* in the Software without restriction, including without limitation the rights
9+
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10+
* copies of the Software, and to permit persons to whom the Software is
11+
* furnished to do so, subject to the following conditions:
12+
*
13+
* The above copyright notice and this permission notice shall be included in
14+
* all copies or substantial portions of the Software.
15+
*
16+
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17+
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18+
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19+
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20+
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21+
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22+
* THE SOFTWARE.
23+
*/
24+
25+
import com.nhaarman.expect.expect
26+
import com.nhaarman.mockito_kotlin.*
27+
import org.junit.Test
28+
import java.io.IOException
29+
import java.math.BigInteger
30+
31+
class CreateInstanceOfImmutableTest {
32+
33+
class ClassToBeMocked {
34+
35+
fun doSomething(c: ClassToBeMocked) {
36+
}
37+
38+
fun doSomethingElse(value: BigInteger): BigInteger {
39+
return value.plus(BigInteger.ONE)
40+
}
41+
}
42+
43+
@Test
44+
fun mockClosedClass() {
45+
/* When */
46+
val result = mock<ClassToBeMocked>()
47+
48+
/* Then */
49+
expect(result).toNotBeNull()
50+
}
51+
52+
@Test
53+
fun anyClosedClass() {
54+
/* Given */
55+
val mock = mock<ClassToBeMocked>()
56+
57+
/* When */
58+
mock.doSomething(mock)
59+
60+
/* Then */
61+
verify(mock).doSomething(any())
62+
}
63+
64+
@Test
65+
fun mockClosedFunction_mockStubbing() {
66+
/* Given */
67+
val mock = mock<ClassToBeMocked> {
68+
on { doSomethingElse(any()) } doReturn (BigInteger.ONE)
69+
}
70+
71+
/* When */
72+
val result = mock.doSomethingElse(BigInteger.TEN)
73+
74+
/* Then */
75+
expect(result).toBe(BigInteger.ONE)
76+
}
77+
78+
@Test
79+
fun mockClosedFunction_whenever() {
80+
/* Given */
81+
val mock = mock<ClassToBeMocked>()
82+
whenever(mock.doSomethingElse(any())).doReturn(BigInteger.ONE)
83+
84+
/* When */
85+
val result = mock.doSomethingElse(BigInteger.TEN)
86+
87+
/* Then */
88+
expect(result).toBe(BigInteger.ONE)
89+
}
90+
91+
/** https://github.com/nhaarman/mockito-kotlin/issues/27 */
92+
@Test
93+
fun anyThrowableWithSingleThrowableConstructor() {
94+
mock<Methods>().apply {
95+
throwableClass(ThrowableClass(IOException()))
96+
verify(this).throwableClass(any())
97+
}
98+
}
99+
100+
interface Methods {
101+
102+
fun throwableClass(t: ThrowableClass)
103+
}
104+
105+
class ThrowableClass(cause: Throwable) : Throwable(cause)
106+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
mock-maker-inline

0 commit comments

Comments
 (0)