Skip to content
Merged
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
195 changes: 171 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,178 @@ 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 methods/function calls.
* In case of Kotlin function calls, these can be either synchronous or suspendable function calls.
*
* Simply put: "**on a call to** the x function **then** return y".
*
* Examples:
*
* ```kotlin
* stubbing(mock) {
* on(mock.someFunction()) doReturn 10
* }
* ```
*
* This function is an alias for [Mockito.when]. So, for more detailed documentation,
* please refer to the Javadoc of that method 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 methods/function calls.
* In case of Kotlin function calls, these can be either synchronous or suspendable function calls.
*
* Simply put: "**on a call to** the x function **then** return y".
*
* Examples:
*
* ```kotlin
* stubbing(mock) {
* on { mock.someFunction() } doReturn 10
* }
* ```
*
* This function is an alias for [Mockito.when]. So, for more detailed documentation,
* please refer to the Javadoc of that method 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 methods/function calls with a generics return type [R].
* In case of Kotlin function calls, these can be either synchronous or suspendable function calls.
*
* 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 is an alias for [Mockito.when]. So, for more detailed documentation,
* please refer to the Javadoc of that method 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 methods/function calls with a generics return type [R].
* In case of Kotlin function calls, these can be either synchronous or suspendable function calls.
*
* 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.
*
* @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 methods/function calls.
* In case of Kotlin function calls, these can be either synchronous or suspendable function calls.
*
* Simply put: "**on a call to** the x function **then** return y".
*
* Examples:
*
* ```kotlin
* stubbing(mock) {
* onBlocking { mock.someFunction() } doReturn 10
* }
* ```
*
* 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()
/**
* Stubs a method/function call 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)
* to throw an exception.
*
* Example:
* ```kotlin
* mock<SynchronousFunctions> {
* doThrow(RuntimeException()).on { string("test") }
* }
* ```
*
* This function is an alias for [whenever]. Please use [whenever] instead.
*
* @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() }
}
}
25 changes: 15 additions & 10 deletions mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Matchers.kt
Original file line number Diff line number Diff line change
Expand Up @@ -35,15 +35,15 @@ import org.mockito.kotlin.internal.unboxValueClass
import org.mockito.kotlin.internal.valueClassInnerClass
import kotlin.reflect.KClass

/** Object argument that is equal to the given value. */
/** Matches an argument that is equal to the given value. */
inline fun <reified T : Any?> eq(value: T): T {
if (T::class.isValue)
return eqValueClass(value)

return ArgumentMatchers.eq(value) ?: value
}

/** Object argument that is the same as the given value. */
/** Matches an argument that is the same as the given value. */
fun <T> same(value: T): T {
return ArgumentMatchers.same(value) ?: value
}
Expand All @@ -61,7 +61,7 @@ inline fun <reified T : Any> anyOrNull(): T {
return ArgumentMatchers.any<T>() ?: createInstance()
}

/** Matches any vararg object, including nulls. */
/** Matches any vararg argument, including nulls. */
inline fun <reified T : Any> anyVararg(): T {
return anyVararg(T::class)
}
Expand All @@ -88,6 +88,7 @@ inline fun <reified T> anyValueClass(): T {
return ArgumentMatchers.any(clazz.valueClassInnerClass().java).boxAsValueClass(clazz)
}

/** Matches an argument that is equal to the given Kotlin value class value. */
inline fun <reified T> eqValueClass(value: T): T {
require(value::class.isValue) { "${value::class.qualifiedName} is not a value class." }

Expand All @@ -101,7 +102,7 @@ inline fun <reified T> eqValueClass(value: T): T {
}

/**
* Creates a custom argument matcher.
* Matches an argument that is matching the given predicate.
* `null` values will never evaluate to `true`.
*
* @param predicate An extension function on [T] that returns `true` when a [T] matches the predicate.
Expand All @@ -113,6 +114,8 @@ inline fun <reified T : Any> argThat(noinline predicate: T.() -> Boolean): T {
}

/**
* Matches an argument that is matching the given [ArgumentMatcher].
*
* Registers a custom ArgumentMatcher. The original Mockito function registers the matcher and returns null,
* here the required type is returned.
*
Expand All @@ -123,6 +126,8 @@ inline fun <reified T : Any> argThat(matcher: ArgumentMatcher<T>): T {
}

/**
* Matches an argument that is matching the given [ArgumentMatcher].
*
* Alias for [argThat].
*
* Creates a custom argument matcher.
Expand All @@ -135,7 +140,7 @@ inline fun <reified T : Any> argForWhich(noinline predicate: T.() -> Boolean): T
}

/**
* Creates a custom argument matcher.
* Matches an argument that is matching the given predicate.
* `null` values will never evaluate to `true`.
*
* @param predicate A function that returns `true` when given [T] matches the predicate.
Expand All @@ -145,33 +150,33 @@ inline fun <reified T : Any> argWhere(noinline predicate: (T) -> Boolean): T {
}

/**
* Argument that implements the given class.
* Matches an argument that is instance of the given class.
*/
inline fun <reified T : Any> isA(): T {
return ArgumentMatchers.isA(T::class.java) ?: createInstance()
}

/**
* `null` argument.
* Matches an argument that is `null`.
*/
fun <T : Any> isNull(): T? = ArgumentMatchers.isNull()

/**
* Not `null` argument.
* Matches an argument that is not `null`.
*/
fun <T : Any> isNotNull(): T? {
return ArgumentMatchers.isNotNull()
}

/**
* Not `null` argument.
* Matches an argument that is not `null`.
*/
fun <T : Any> notNull(): T? {
return ArgumentMatchers.notNull()
}

/**
* Object argument that is reflection-equal to the given value with support for excluding
* Matches an argument that is reflection-equal to the given value with support for excluding
* selected fields from a class.
*/
inline fun <reified T : Any> refEq(value: T, vararg excludeFields: String): T {
Expand Down
Loading