Skip to content

Commit 3011f9f

Browse files
committed
Enhance and document stubbing API (apply infix and reified generics + apply whenever() with lambda arg as default in test cases). Also, deprecated methods onBlocking(methodCall), onGeneric(methodCall), wheneverBlocking(methodCall) to unify towards whenever(methodCall) and on(methodCall) as universal methods to stub any method call, being either synchronous, suspendable or with generics return type.
1 parent b4f703a commit 3011f9f

File tree

14 files changed

+1070
-193
lines changed

14 files changed

+1070
-193
lines changed

mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KStubbing.kt

Lines changed: 204 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -28,18 +28,31 @@ package org.mockito.kotlin
2828
import org.mockito.kotlin.internal.createInstance
2929
import kotlinx.coroutines.runBlocking
3030
import org.mockito.Mockito
31+
import org.mockito.Mockito.`when`
3132
import org.mockito.exceptions.misusing.NotAMockException
3233
import org.mockito.stubbing.OngoingStubbing
3334
import org.mockito.stubbing.Stubber
3435
import kotlin.reflect.KClass
3536

37+
/**
38+
* Stub a mock with given stubbing configuration.
39+
*
40+
* @param mock the mock to stub.
41+
* @param stubbing the stubbing configuration to apply to the mock.
42+
*/
3643
inline fun <T : Any> stubbing(
3744
mock: T,
3845
stubbing: KStubbing<T>.(T) -> Unit
3946
) {
4047
KStubbing(mock).stubbing(mock)
4148
}
4249

50+
/**
51+
* Stub a mock with given stubbing configuration.
52+
*
53+
* @receiver the mock to stub.
54+
* @param stubbing the stubbing configuration to apply to the mock.
55+
*/
4356
inline fun <T : Any> T.stub(stubbing: KStubbing<T>.(T) -> Unit): T {
4457
return apply { KStubbing(this).stubbing(this) }
4558
}
@@ -49,44 +62,211 @@ class KStubbing<out T : Any>(val mock: T) {
4962
if (!mockingDetails(mock).isMock) throw NotAMockException("Stubbing target is not a mock!")
5063
}
5164

52-
fun <R> on(methodCall: R): OngoingStubbing<R> = Mockito.`when`(methodCall)
65+
/**
66+
* Enables stubbing java methods or kotlin functions. Use it when you want the mock to return
67+
* a particular value when particular method/function is being called.
68+
* The kotlin function call to be stubbed can be either a synchronous or suspendable function.
69+
*
70+
* Simply put: "**on a call to** the x function **then** return y".
71+
*
72+
* Examples:
73+
*
74+
* ```kotlin
75+
* stubbing(mock) {
76+
* on (mock.someFunction()) doReturn 10
77+
* }
78+
* ```
79+
*
80+
* This function acts as an alias for [Mockito.when], as `when` is a keyword in kotlin and as such
81+
* the Mockito method can only be called by wrapping the method name in backticks, e.g. `` `when` ``.
82+
* To reduce the noise of backticks in your code, you can use this the function [whenever] instead.
83+
*
84+
* For more detail documentation, please refer to the Javadoc in the [Mockito] class.
85+
*
86+
* For stubbing Unit functions (or Java void methods) with throwables, see: [Mockito.doThrow].
87+
*
88+
* @param methodCall method call to be stubbed.
89+
* @return OngoingStubbing object used to stub fluently.
90+
* ***Do not*** create a reference to this returned object.
91+
*/
92+
inline fun <reified R> on(methodCall: R): OngoingStubbing<R> = whenever(methodCall)
5393

54-
fun <R : Any> onGeneric(methodCall: T.() -> R?, c: KClass<R>): OngoingStubbing<R> {
55-
val r = try {
56-
mock.methodCall()
94+
/**
95+
* Enables stubbing java methods or kotlin functions. Use it when you want the mock to return
96+
* a particular value when particular method/function is being called.
97+
* The kotlin function call to be stubbed can be either a synchronous or suspendable function.
98+
*
99+
* Simply put: "**on a call to** the x function **then** return y".
100+
*
101+
* Examples:
102+
*
103+
* ```kotlin
104+
* stubbing(mock) {
105+
* on { mock.someFunction() } doReturn 10
106+
* }
107+
* ```
108+
*
109+
* This function acts as an alias for [Mockito.when], as `when` is a keyword in kotlin and as such
110+
* the Mockito method can only be called by wrapping the method name in backticks, e.g. `` `when` ``.
111+
* To reduce the noise of backticks in your code, you can use this the function [whenever] instead.
112+
*
113+
* For more detail documentation, please refer to the Javadoc in the [Mockito] class.
114+
*
115+
* For stubbing Unit functions (or Java void methods) with throwables, see: [Mockito.doThrow].
116+
*
117+
* @param methodCall method call to be stubbed.
118+
* @return OngoingStubbing object used to stub fluently.
119+
* ***Do not*** create a reference to this returned object.
120+
*/
121+
fun <R> on(methodCall: suspend T.() -> R): OngoingStubbing<R> {
122+
return try {
123+
whenever { mock.methodCall() }
57124
} catch (e: NullPointerException) {
125+
throw MockitoKotlinException(
126+
"NullPointerException thrown when stubbing.\nThis may be due to two reasons:\n\t- The method you're trying to stub threw an NPE: look at the stack trace below;\n\t- You're trying to stub a generic method: try `onGeneric` instead.",
127+
e
128+
)
129+
}
130+
}
131+
132+
/**
133+
* Enables stubbing java methods or kotlin functions with a generics return type [R].
134+
* Use it when you want the mock to return a particular value when particular method/function
135+
* is being called.
136+
* The kotlin function call to be stubbed can be either a synchronous or suspendable function.
137+
*
138+
* Simply put: "**on a call to** the x function **then** return y".
139+
*
140+
* Examples:
141+
*
142+
* ```kotlin
143+
* interface GenericMethods<T> {
144+
* fun genericMethod(): T
145+
* }
146+
*
147+
* mock<GenericMethods<Int>> {
148+
* onGeneric({ genericMethod() }, Int::class) doReturn 10
149+
* }
150+
* ```
151+
*
152+
* This function acts as an alias for [Mockito.when], as `when` is a keyword in kotlin and as such
153+
* the Mockito method can only be called by wrapping the method name in backticks, e.g. `` `when` ``.
154+
* To reduce the noise of backticks in your code, you can use this the function [whenever] instead.
155+
*
156+
* For more detail documentation, please refer to the Javadoc in the [Mockito] class.
157+
*
158+
* For stubbing Unit functions (or Java void methods) with throwables, see: [Mockito.doThrow].
159+
*
160+
* @param methodCall method call to be stubbed.
161+
* @param clazz the generics type.
162+
* @return OngoingStubbing object used to stub fluently.
163+
* ***Do not*** create a reference to this returned object.
164+
*/
165+
fun <R : Any> onGeneric(methodCall: suspend T.() -> R?, clazz: KClass<R>): OngoingStubbing<R> {
166+
val r = try {
167+
runBlocking { mock.methodCall() }
168+
} catch (_: NullPointerException) {
58169
// An NPE may be thrown by the Kotlin type system when the MockMethodInterceptor returns a
59170
// null value for a non-nullable generic type.
60171
// We catch this NPE to return a valid instance.
61172
// The Mockito state has already been modified at this point to reflect
62173
// the wanted changes.
63-
createInstance(c)
174+
createInstance(clazz)
175+
64176
}
65-
return Mockito.`when`(r)
177+
return `when`<R?>(r)!!
66178
}
67179

68-
inline fun <reified R : Any> onGeneric(noinline methodCall: T.() -> R?): OngoingStubbing<R> {
180+
/**
181+
* Enables stubbing java methods or kotlin functions with a generics return type [R].
182+
* Use it when you want the mock to return a particular value when particular method/function
183+
* is being called.
184+
* The kotlin function call to be stubbed can be either a synchronous or suspendable function.
185+
*
186+
* Simply put: "**on a call to** the x function **then** return y".
187+
*
188+
* Examples:
189+
*
190+
* ```kotlin
191+
* interface GenericMethods<T> {
192+
* fun genericMethod(): T
193+
* }
194+
*
195+
* mock<GenericMethods<Int>> {
196+
* onGeneric { genericMethod() } doReturn 10
197+
* }
198+
* ```
199+
*
200+
* This is a deprecated alias for [on]. Please use [on] instead.
201+
*
202+
* For more detailed documentation, please refer to [on].
203+
*
204+
* @param methodCall method call to be stubbed.
205+
* @return OngoingStubbing object used to stub fluently.
206+
* ***Do not*** create a reference to this returned object.
207+
*/
208+
@Deprecated("Use on { mock.methodCall() } instead")
209+
inline fun <reified R : Any> onGeneric(noinline methodCall: suspend T.() -> R?): OngoingStubbing<R> {
69210
return onGeneric(methodCall, R::class)
70211
}
71212

72-
fun <R> on(methodCall: T.() -> R): OngoingStubbing<R> {
73-
return try {
74-
Mockito.`when`(mock.methodCall())
75-
} catch (e: NullPointerException) {
76-
throw MockitoKotlinException(
77-
"NullPointerException thrown when stubbing.\nThis may be due to two reasons:\n\t- The method you're trying to stub threw an NPE: look at the stack trace below;\n\t- You're trying to stub a generic method: try `onGeneric` instead.",
78-
e
79-
)
80-
}
81-
}
82-
83-
fun <T : Any, R> KStubbing<T>.onBlocking(
84-
m: suspend T.() -> R
85-
): OngoingStubbing<R> {
86-
return runBlocking { Mockito.`when`(mock.m()) }
213+
/**
214+
* Enables stubbing a (suspendable) kotlin functions. Use it when you want the mock to return
215+
* a particular value when particular function is being called.
216+
* The kotlin function call to be stubbed can be either a synchronous or suspendable function.
217+
*
218+
* Simply put: "**on a call to** the x function **then** return y".
219+
*
220+
* Examples:
221+
*
222+
* ```kotlin
223+
* stubbing(mock) {
224+
* onBlocking { mock.someFunction() } doReturn 10
225+
* }
226+
* ```
227+
*
228+
* This function acts as an alias for [Mockito.when], as `when` is a keyword in kotlin and as such
229+
* the Mockito method can only be called by wrapping the method name in backticks, e.g. `` `when` ``.
230+
* To reduce the noise of backticks in your code, you can use this function [on] instead.
231+
*
232+
* For more detail documentation, please refer to the Javadoc in the [Mockito] class.
233+
*
234+
* For stubbing Unit functions (or Java void methods) with throwables, see: [Mockito.doThrow].
235+
*
236+
* This is a deprecated alias for [on]. Please use [on] instead.
237+
*
238+
* @param methodCall (regular or suspendable) lambda, wrapping the function call to be stubbed.
239+
* @return OngoingStubbing object used to stub fluently.
240+
* ***Do not*** create a reference to this returned object.
241+
*/
242+
@Deprecated("Use on { methodCall } instead")
243+
fun <T : Any, R> KStubbing<T>.onBlocking(methodCall: suspend T.() -> R): OngoingStubbing<R> {
244+
return runBlocking { `when`<R>(mock.methodCall())!! }
87245
}
88246

89-
fun Stubber.on(methodCall: T.() -> Unit) {
90-
this.`when`(mock).methodCall()
247+
/**
248+
* Sets the method call to be stubbed in a reverse manner, as part of a mock being created.
249+
* You can reverse stub either synchronous as well as suspendable function calls.
250+
*
251+
* Reverse stubbing is especially useful when stubbing a void method (or Unit function) as
252+
* the regular approach of ongoing stubbing through [org.mockito.kotlin.whenever] leads to
253+
* problems in case of void methods (or Unit functions): the java compiler does not like void
254+
* methods inside brackets...
255+
*
256+
* Example:
257+
* ```kotlin
258+
* mock<SynchronousFunctions> {
259+
* doReturn("Test").on { stringResult() }
260+
* }
261+
* ```
262+
* Warning: Only one method call can be stubbed in the function. Subsequent method calls are ignored!
263+
*
264+
* This function acts as an alias for [whenever].
265+
* For more detailed documentation, please refer to [whenever].
266+
*
267+
* @param methodCall (regular or suspendable) lambda, wrapping the method/function call to be stubbed.
268+
*/
269+
infix fun Stubber.on(methodCall: suspend T.() -> Unit) {
270+
this.whenever(mock) { methodCall() }
91271
}
92272
}

0 commit comments

Comments
 (0)