Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
228 changes: 204 additions & 24 deletions mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KStubbing.kt
Original file line number Diff line number Diff line change
Expand Up @@ -28,18 +28,31 @@ package org.mockito.kotlin
import org.mockito.kotlin.internal.createInstance
import kotlinx.coroutines.runBlocking
import org.mockito.Mockito
import org.mockito.Mockito.`when`
import org.mockito.exceptions.misusing.NotAMockException
import org.mockito.stubbing.OngoingStubbing
import org.mockito.stubbing.Stubber
import kotlin.reflect.KClass

/**
* Stub a mock with given stubbing configuration.
*
* @param mock the mock to stub.
* @param stubbing the stubbing configuration to apply to the mock.
*/
inline fun <T : Any> stubbing(
mock: T,
stubbing: KStubbing<T>.(T) -> Unit
) {
KStubbing(mock).stubbing(mock)
}

/**
* Stub a mock with given stubbing configuration.
*
* @receiver the mock to stub.
* @param stubbing the stubbing configuration to apply to the mock.
*/
inline fun <T : Any> T.stub(stubbing: KStubbing<T>.(T) -> Unit): T {
return apply { KStubbing(this).stubbing(this) }
}
Expand All @@ -49,44 +62,211 @@ class KStubbing<out T : Any>(val mock: T) {
if (!mockingDetails(mock).isMock) throw NotAMockException("Stubbing target is not a mock!")
}

fun <R> on(methodCall: R): OngoingStubbing<R> = Mockito.`when`(methodCall)
/**
* Enables stubbing java methods or kotlin functions. Use it when you want the mock to return
* a particular value when particular method/function is being called.
* The kotlin function call to be stubbed can be either a synchronous or suspendable function.
*
* Simply put: "**on a call to** the x function **then** return y".
*
* Examples:
*
* ```kotlin
* stubbing(mock) {
* on (mock.someFunction()) doReturn 10
* }
* ```
*
* This function acts as an alias for [Mockito.when], as `when` is a keyword in kotlin and as such
* the Mockito method can only be called by wrapping the method name in backticks, e.g. `` `when` ``.
* To reduce the noise of backticks in your code, you can use this the function [whenever] instead.
*
* For more detail documentation, please refer to the Javadoc in the [Mockito] class.
*
* For stubbing Unit functions (or Java void methods) with throwables, see: [Mockito.doThrow].
*
* @param methodCall method call to be stubbed.
* @return OngoingStubbing object used to stub fluently.
* ***Do not*** create a reference to this returned object.
*/
inline fun <reified R> on(methodCall: R): OngoingStubbing<R> = whenever(methodCall)

fun <R : Any> onGeneric(methodCall: T.() -> R?, c: KClass<R>): OngoingStubbing<R> {
val r = try {
mock.methodCall()
/**
* Enables stubbing java methods or kotlin functions. Use it when you want the mock to return
* a particular value when particular method/function is being called.
* The kotlin function call to be stubbed can be either a synchronous or suspendable function.
*
* Simply put: "**on a call to** the x function **then** return y".
*
* Examples:
*
* ```kotlin
* stubbing(mock) {
* on { mock.someFunction() } doReturn 10
* }
* ```
*
* This function acts as an alias for [Mockito.when], as `when` is a keyword in kotlin and as such
* the Mockito method can only be called by wrapping the method name in backticks, e.g. `` `when` ``.
* To reduce the noise of backticks in your code, you can use this the function [whenever] instead.
*
* For more detail documentation, please refer to the Javadoc in the [Mockito] class.
*
* For stubbing Unit functions (or Java void methods) with throwables, see: [Mockito.doThrow].
*
* @param methodCall method call to be stubbed.
* @return OngoingStubbing object used to stub fluently.
* ***Do not*** create a reference to this returned object.
*/
fun <R> on(methodCall: suspend T.() -> R): OngoingStubbing<R> {
return try {
whenever { mock.methodCall() }
} catch (e: NullPointerException) {
throw MockitoKotlinException(
"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.",
e
)
}
}

/**
* Enables stubbing java methods or kotlin functions with a generics return type [R].
* Use it when you want the mock to return a particular value when particular method/function
* is being called.
* The kotlin function call to be stubbed can be either a synchronous or suspendable function.
*
* Simply put: "**on a call to** the x function **then** return y".
*
* Examples:
*
* ```kotlin
* interface GenericMethods<T> {
* fun genericMethod(): T
* }
*
* mock<GenericMethods<Int>> {
* onGeneric({ genericMethod() }, Int::class) doReturn 10
* }
* ```
*
* This function acts as an alias for [Mockito.when], as `when` is a keyword in kotlin and as such
* the Mockito method can only be called by wrapping the method name in backticks, e.g. `` `when` ``.
* To reduce the noise of backticks in your code, you can use this the function [whenever] instead.
*
* For more detail documentation, please refer to the Javadoc in the [Mockito] class.
*
* For stubbing Unit functions (or Java void methods) with throwables, see: [Mockito.doThrow].
*
* @param methodCall method call to be stubbed.
* @param clazz the generics type.
* @return OngoingStubbing object used to stub fluently.
* ***Do not*** create a reference to this returned object.
*/
fun <R : Any> onGeneric(methodCall: suspend T.() -> R?, clazz: KClass<R>): OngoingStubbing<R> {
val r = try {
runBlocking { mock.methodCall() }
} catch (_: NullPointerException) {
// An NPE may be thrown by the Kotlin type system when the MockMethodInterceptor returns a
// null value for a non-nullable generic type.
// We catch this NPE to return a valid instance.
// The Mockito state has already been modified at this point to reflect
// the wanted changes.
createInstance(c)
createInstance(clazz)

}
return Mockito.`when`(r)
return `when`<R?>(r)!!
}

inline fun <reified R : Any> onGeneric(noinline methodCall: T.() -> R?): OngoingStubbing<R> {
/**
* Enables stubbing java methods or kotlin functions with a generics return type [R].
* Use it when you want the mock to return a particular value when particular method/function
* is being called.
* The kotlin function call to be stubbed can be either a synchronous or suspendable function.
*
* Simply put: "**on a call to** the x function **then** return y".
*
* Examples:
*
* ```kotlin
* interface GenericMethods<T> {
* fun genericMethod(): T
* }
*
* mock<GenericMethods<Int>> {
* onGeneric { genericMethod() } doReturn 10
* }
* ```
*
* This is a deprecated alias for [on]. Please use [on] instead.
*
* For more detailed documentation, please refer to [on].
*
* @param methodCall method call to be stubbed.
* @return OngoingStubbing object used to stub fluently.
* ***Do not*** create a reference to this returned object.
*/
@Deprecated("Use on { mock.methodCall() } instead")
inline fun <reified R : Any> onGeneric(noinline methodCall: suspend T.() -> R?): OngoingStubbing<R> {
return onGeneric(methodCall, R::class)
}

fun <R> on(methodCall: T.() -> R): OngoingStubbing<R> {
return try {
Mockito.`when`(mock.methodCall())
} catch (e: NullPointerException) {
throw MockitoKotlinException(
"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.",
e
)
}
}

fun <T : Any, R> KStubbing<T>.onBlocking(
m: suspend T.() -> R
): OngoingStubbing<R> {
return runBlocking { Mockito.`when`(mock.m()) }
/**
* Enables stubbing a (suspendable) kotlin functions. Use it when you want the mock to return
* a particular value when particular function is being called.
* The kotlin function call to be stubbed can be either a synchronous or suspendable function.
*
* Simply put: "**on a call to** the x function **then** return y".
*
* Examples:
*
* ```kotlin
* stubbing(mock) {
* onBlocking { mock.someFunction() } doReturn 10
* }
* ```
*
* This function acts as an alias for [Mockito.when], as `when` is a keyword in kotlin and as such
* the Mockito method can only be called by wrapping the method name in backticks, e.g. `` `when` ``.
* To reduce the noise of backticks in your code, you can use this function [on] instead.
*
* For more detail documentation, please refer to the Javadoc in the [Mockito] class.
*
* For stubbing Unit functions (or Java void methods) with throwables, see: [Mockito.doThrow].
*
* This is a deprecated alias for [on]. Please use [on] instead.
*
* @param methodCall (regular or suspendable) lambda, wrapping the function call to be stubbed.
* @return OngoingStubbing object used to stub fluently.
* ***Do not*** create a reference to this returned object.
*/
@Deprecated("Use on { methodCall } instead")
fun <T : Any, R> KStubbing<T>.onBlocking(methodCall: suspend T.() -> R): OngoingStubbing<R> {
return runBlocking { `when`<R>(mock.methodCall())!! }
}

fun Stubber.on(methodCall: T.() -> Unit) {
this.`when`(mock).methodCall()
/**
* Sets the method call to be stubbed in a reverse manner, as part of a mock being created.
* You can reverse stub either synchronous as well as suspendable function calls.
*
* Reverse stubbing is especially useful when stubbing a void method (or Unit function) as
* the regular approach of ongoing stubbing through [org.mockito.kotlin.whenever] leads to
* problems in case of void methods (or Unit functions): the java compiler does not like void
* methods inside brackets...
*
* Example:
* ```kotlin
* mock<SynchronousFunctions> {
* doReturn("Test").on { stringResult() }
* }
* ```
* Warning: Only one method call can be stubbed in the function. Subsequent method calls are ignored!
*
* This function acts as an alias for [whenever].
* For more detailed documentation, please refer to [whenever].
*
* @param methodCall (regular or suspendable) lambda, wrapping the method/function call to be stubbed.
*/
infix fun Stubber.on(methodCall: suspend T.() -> Unit) {
this.whenever(mock) { methodCall() }
}
}
Loading