diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KStubbing.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KStubbing.kt index 31a7c75c..b7c7c7d5 100644 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KStubbing.kt +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KStubbing.kt @@ -28,11 +28,18 @@ 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 stubbing( mock: T, stubbing: KStubbing.(T) -> Unit @@ -40,6 +47,12 @@ inline fun stubbing( 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.stub(stubbing: KStubbing.(T) -> Unit): T { return apply { KStubbing(this).stubbing(this) } } @@ -49,44 +62,211 @@ class KStubbing(val mock: T) { if (!mockingDetails(mock).isMock) throw NotAMockException("Stubbing target is not a mock!") } - fun on(methodCall: R): OngoingStubbing = 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 on(methodCall: R): OngoingStubbing = whenever(methodCall) - fun onGeneric(methodCall: T.() -> R?, c: KClass): OngoingStubbing { - 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 on(methodCall: suspend T.() -> R): OngoingStubbing { + 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 { + * fun genericMethod(): T + * } + * + * mock> { + * 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 onGeneric(methodCall: suspend T.() -> R?, clazz: KClass): OngoingStubbing { + 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)!! } - inline fun onGeneric(noinline methodCall: T.() -> R?): OngoingStubbing { + /** + * 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 { + * fun genericMethod(): T + * } + * + * mock> { + * 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 onGeneric(noinline methodCall: suspend T.() -> R?): OngoingStubbing { return onGeneric(methodCall, R::class) } - fun on(methodCall: T.() -> R): OngoingStubbing { - 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 KStubbing.onBlocking( - m: suspend T.() -> R - ): OngoingStubbing { - 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 KStubbing.onBlocking(methodCall: suspend T.() -> R): OngoingStubbing { + return runBlocking { `when`(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 { + * 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() } } } diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/OngoingStubbing.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/OngoingStubbing.kt index 39c6dcb2..7851bf34 100644 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/OngoingStubbing.kt +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/OngoingStubbing.kt @@ -28,6 +28,7 @@ package org.mockito.kotlin import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.runBlocking import org.mockito.Mockito +import org.mockito.Mockito.`when` import org.mockito.kotlin.internal.KAnswer import org.mockito.kotlin.internal.SuspendableAnswer import org.mockito.stubbing.Answer @@ -35,107 +36,309 @@ import org.mockito.stubbing.OngoingStubbing import kotlin.reflect.KClass /** - * Enables stubbing methods. Use it when you want the mock to return particular value when particular method is called. + * 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. * - * Alias for [Mockito.when]. + * Simply put: "**Whenever** the x function is being called **then** return y". + * + * Examples: + * + * ```kotlin + * whenever(mock.someFunction()) doReturn 10 + * + * //you can use flexible argument matchers, e.g: + * whenever(mock.someFunction(anyString())) 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 whenever(methodCall: T): OngoingStubbing { + return `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 lambda with the method/function call to be stubbed can be either a synchronous (regular) + * or suspend lambda. + * + * **Warning**: Only the first method/function call in the lambda will be stubbed, other methods/functions calls are ignored! + * + * Simply put: "**Whenever** the x function is being called **then** return y". + * + * Examples: + * + * ```kotlin + * whenever { mock.someFunction() } doReturn 10 + * + * //you can use flexible argument matchers, e.g: + * whenever { mock.someFunction(anyString()) } 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. + * Next to that, this function will take care of handling with suspend lambda, to ease the stubbing + * of a suspendable function call. + * + * For more detailed documentation, please refer to the Javadoc in the [Mockito] class. + * + * For stubbing Unit functions (or Java void methods), see: [Mockito.doThrow]. + * + * @param methodCall (regular or suspendable) lambda, wrapping the method/function call to be stubbed. + * @return OngoingStubbing object used to stub fluently. + * ***Do not*** create a reference to this returned object. */ -@Suppress("NOTHING_TO_INLINE") -inline fun whenever(methodCall: T): OngoingStubbing { - return Mockito.`when`(methodCall)!! +fun whenever(methodCall: suspend CoroutineScope.() -> T): OngoingStubbing { + return runBlocking { `when`(methodCall())!! } } /** - * Enables stubbing suspending methods. Use it when you want the mock to return particular value when particular suspending method is called. + * 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 lambda with the function call to be stubbed can be either a synchronous (regular) + * or suspend lambda. + * + * This is a deprecated alias for [whenever]. Please use [whenever] instead. * - * Warning: Only one method call can be stubbed in the function. - * other method calls are ignored! + * For more detailed documentation, please refer to [whenever]. + * + * @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 whenever { mock.methodCall() } instead") fun wheneverBlocking(methodCall: suspend CoroutineScope.() -> T): OngoingStubbing { - return runBlocking { Mockito.`when`(methodCall()) } + return whenever(methodCall) } /** - * Sets a return value to be returned when the method is called. + * Sets a return value to be returned when the method/function is called. E.g: + * + * ```kotlin + * whenever { mock.someMethod() } doReturn 10 + * ``` * - * Alias for [OngoingStubbing.thenReturn]. + * This function acts as an alias for Mockito's [OngoingStubbing.thenReturn], adding the infix + * functionality and extended type inference to it. + * + * @param value return value for the method/function invocation. + * @return OngoingStubbing object used to stub fluently. + * ***Do not*** create a reference to this returned object. */ -infix fun OngoingStubbing.doReturn(t: T): OngoingStubbing { - return thenReturn(t) +inline infix fun OngoingStubbing.doReturn(value: T): OngoingStubbing { + return thenReturn(value) } /** - * Sets consecutive return values to be returned when the method is called. + * Sets a return values to be returned when the method/function is called consecutively. E.g: + * + * ```kotlin + * whenever { mock.someMethod() }.doReturn(10, 20) + * ``` + * You can specify [values] to be returned on consecutive invocations. + * In that case the last value determines the behavior of further consecutive invocations. * - * Alias for [OngoingStubbing.thenReturn]. + * This function acts as an alias for Mockito's [OngoingStubbing.thenReturn], adding extended + * type inference to it. + * + * @param value return value for the first method/function invocation. + * @param values return values for the next method/function invocations. + * @return OngoingStubbing object used to stub fluently. + * ***Do not*** create a reference to this returned object. */ -fun OngoingStubbing.doReturn(t: T, vararg ts: T): OngoingStubbing { - return thenReturn(t, *ts) +inline fun OngoingStubbing.doReturn(value: T, vararg values: T): OngoingStubbing { + return doReturnConsecutively(value, *values) } /** - * Sets consecutive return values to be returned when the method is called. + * Sets a return values to be returned when the method/function is called consecutively. E.g: + * + * ```kotlin + * whenever { mock.someMethod() }.doReturnConsecutively(10, 20) + * ``` + * You can specify [values] to be returned on consecutive invocations. + * In that case the last value determines the behavior of further consecutive invocations. + * + * This function acts as an alias for Mockito's [OngoingStubbing.thenReturn], adding extended + * type inference to it. + * + * @param value return value for the first method/function invocation. + * @param values return values for the next method/function invocations. + * @return OngoingStubbing object used to stub fluently. + * ***Do not*** create a reference to this returned object. */ -inline infix fun OngoingStubbing.doReturnConsecutively(ts: List): OngoingStubbing { - return thenReturn( - ts[0], - *ts.drop(1).toTypedArray() - ) +inline fun OngoingStubbing.doReturnConsecutively(value: T, vararg values: T): OngoingStubbing { + return doReturnConsecutively(listOf(value, *values)) } /** - * Sets Throwable objects to be thrown when the method is called. + * Sets a return values to be returned when the method/function is called consecutively. E.g: + * + * ```kotlin + * whenever { mock.someMethod() } doReturnConsecutively listOf(10, 20) + * ``` * - * Alias for [OngoingStubbing.thenThrow]. + * The last value in [values] determines the behavior of further consecutive invocations. + * + * This function acts as an alias for Mockito's [OngoingStubbing.thenReturn], adding the infix + * functionality and extended type inference to it. + * + * @param values return values for the consecutive method/function invocations. + * @return OngoingStubbing object used to stub fluently. + * ***Do not*** create a reference to this returned object. */ -infix fun OngoingStubbing.doThrow(t: Throwable): OngoingStubbing { - return thenThrow(t) +inline infix fun OngoingStubbing.doReturnConsecutively(values: List): OngoingStubbing { + return thenReturn(values.first(), *values.drop(1).toTypedArray()) } /** - * Sets Throwable objects to be thrown when the method is called. + * Sets Throwable instance to be thrown when the method/function is called. E.g: + * + * ```kotlin + * whenever { mock.someFunction() } doThrow RuntimeException() + * ``` + * + * This function acts as an alias for Mockito's [OngoingStubbing.thenThrow], adding the + * infix functionality to it. * - * Alias for [OngoingStubbing.doThrow]. + * @param throwable to be thrown on method/function invocations. + * @return OngoingStubbing object used to stub fluently. + * ***Do not*** create a reference to this returned object. */ -fun OngoingStubbing.doThrow( - t: Throwable, - vararg ts: Throwable -): OngoingStubbing { - return thenThrow(t, *ts) +infix fun OngoingStubbing.doThrow(throwable: Throwable): OngoingStubbing { + return thenThrow(throwable) } /** - * Sets a Throwable type to be thrown when the method is called. + * Sets Throwable instance(s) to be thrown when the method/function is called consecutively. E.g: + * + * ```kotlin + * whenever { mock.someFunction() }.doThrow(RuntimeException(), IOException()) + * ``` + * + * You can specify [throwables] to be thrown for consecutive invocations. + * In that case the last throwable determines the behavior of further consecutive invocations. + * + * This function acts as an alias for Mockito's [OngoingStubbing.thenThrow]. + * + * @param throwable to be thrown on the first method/function invocation. + * @param throwables to be thrown on the next method/function invocations. + * @return OngoingStubbing object used to stub fluently. + * ***Do not*** create a reference to this returned object. */ -infix fun OngoingStubbing.doThrow(t: KClass): OngoingStubbing { - return thenThrow(t.java) +fun OngoingStubbing.doThrow(throwable: Throwable, vararg throwables: Throwable): OngoingStubbing { + return thenThrow(throwable, *throwables) } /** - * Sets Throwable classes to be thrown when the method is called. + * Sets a Throwable type to be thrown when the method/function is called. E.g: + * + * ```kotlin + * whenever { mock.someFunction() } doThrow IllegalArgumentException::class + * ``` + * + * This function acts as an alias for Mockito's [OngoingStubbing.thenThrow], accepting the + * throwable type as [KClass] and adding the infix functionality to it. + * + * @param throwableType to be thrown on the method/function invocation. + * @return OngoingStubbing object used to stub fluently. + * ***Do not*** create a reference to this returned object. */ -fun OngoingStubbing.doThrow( - t: KClass, - vararg ts: KClass -): OngoingStubbing { - return thenThrow(t.java, *ts.map { it.java }.toTypedArray()) +infix fun OngoingStubbing.doThrow(throwableType: KClass): OngoingStubbing { + return thenThrow(throwableType.java) } /** - * Sets a generic Answer for the method. + * Sets a Throwable type to be thrown when the method is called consecutively. E.g: + * + * ```kotlin + * whenever { mock.someFunction() }.doThrow(IllegalArgumentException::class, NullPointerException::class) + * ``` * - * Alias for [OngoingStubbing.thenAnswer]. + * You can specify [throwableTypes] to be thrown for consecutive invocations. + * In that case the last throwable type determines the behavior of further consecutive invocations. + * + * This function acts as an alias for Mockito's [OngoingStubbing.thenThrow], accepting the + * throwable types as [KClass]. + * + * @param throwableType to be thrown on the first method/function invocation. + * @param throwableTypes to be thrown on the next method/function invocations. + * @return OngoingStubbing object used to stub fluently. + * ***Do not*** create a reference to this returned object. + */ +fun OngoingStubbing.doThrow(throwableType: KClass, vararg throwableTypes: KClass): OngoingStubbing { + return thenThrow(throwableType.java, *throwableTypes.map { it.java }.toTypedArray()) +} + +/** + * Sets a generic answer for when the method/function is called. E.g: + * + * ```kotlin + * val answer = Answer { "result" } + * whenever { mock.someFunction() } doAnswer answer + * ``` + * + * This function acts as an alias for Mockito's [OngoingStubbing.thenAnswer], adding the infix + * functionality to it. + * + * @param answer to be applied on the method/function invocation. + * @return OngoingStubbing object used to stub fluently. + * ***Do not*** create a reference to this returned object. */ infix fun OngoingStubbing.doAnswer(answer: Answer<*>): OngoingStubbing { return thenAnswer(answer) } /** - * Sets a generic Answer for the method using a lambda. + * Sets an answer for when the method/function is called, specified by a lambda. E.g: + * + * ```kotlin + * whenever { mock.someFunction() } doAnswer { "result" } + * ``` + * + * This function acts as an alias for Mockito's [OngoingStubbing.thenAnswer], adding the infix + * functionality to it. + * + * @param answer to be applied on the method/function invocation. + * @return OngoingStubbing object used to stub fluently. + * ***Do not*** create a reference to this returned object. */ infix fun OngoingStubbing.doAnswer(answer: (KInvocationOnMock) -> T?): OngoingStubbing { return thenAnswer(KAnswer(answer)) } +/** + * Sets an answer for when the suspendable function is called, specified by a suspendable lambda. E.g: + * + * ```kotlin + * whenever { mock.someFunction() } doAnswer { + * delay(1) + * "result" + * } + * ``` + * + * This function acts as an alias for Mockito's [OngoingStubbing.thenAnswer], adding the infix + * functionality to it. + * Also, this function will wrap the answer lambda in a SuspendableAnswer object, to wire the + * suspendable lambda properly into the Kotlin's coroutine context on invocation of the stubbed + * suspendable function. + * + * @param answer to be applied on the suspendable function invocation. + * @return OngoingStubbing object used to stub fluently. + * ***Do not*** create a reference to this returned object. + */ infix fun OngoingStubbing.doSuspendableAnswer(answer: suspend (KInvocationOnMock) -> T?): OngoingStubbing { return thenAnswer(SuspendableAnswer(answer)) } diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Stubber.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Stubber.kt index 9b521de4..cdedb2a5 100644 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Stubber.kt +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Stubber.kt @@ -27,56 +27,345 @@ package org.mockito.kotlin import kotlinx.coroutines.runBlocking import org.mockito.Mockito -import org.mockito.invocation.InvocationOnMock import org.mockito.kotlin.internal.SuspendableAnswer -import org.mockito.stubbing.OngoingStubbing import org.mockito.stubbing.Stubber import kotlin.reflect.KClass -fun doAnswer(answer: (InvocationOnMock) -> T?): Stubber { - return Mockito.doAnswer { answer(it) }!! +/** + * Sets a generic answer, specified with a lambda, to be applied in reverse stubbing. + * + * 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 + * doAnswer { "result" }.whenever(mock).someMethod() + * ``` + * + * This function acts as an alias for Mockito's [Mockito.doAnswer]. + * + * See examples in javadoc for [Mockito] class + * + * @param answer to answer to apply when the stubbed method/function is called. + * @return Stubber object used to stub fluently. + */ +fun doAnswer(answer: (KInvocationOnMock) -> T?): Stubber { + return Mockito.doAnswer { answer.invoke(KInvocationOnMock(it)) } } +/** + * Sets a generic answer, specified with a suspendable lambda, to be applied in reverse stubbing. + * + * 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 + * doSuspendableAnswer { + * delay(1) + * "result" + * }.whenever(mock).someMethod() + * ``` + * + * This function acts as an alias for Mockito's [Mockito.doAnswer], this function will also wrap + * the answer lambda in a SuspendableAnswer object, to wire the suspendable lambda properly + * into the Kotlin's coroutine context on invocation of the stubbed suspendable function. + * + * See examples in javadoc for [Mockito] class + * + * @param answer to answer to apply when the stubbed method/function is called. + * @return Stubber object used to stub fluently. + */ fun doSuspendableAnswer(answer: suspend (KInvocationOnMock) -> T?): Stubber { return Mockito.doAnswer(SuspendableAnswer(answer)) } +/** + * Sets to call the real implementation of a method in a mock, to be applied in reverse stubbing. + * + * 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 + * doCallRealMethod().whenever(mock).someMethod() + * ``` + * + * This function acts as an alias for Mockito's [Mockito.doCallRealMethod]. + * + * See examples in javadoc for [Mockito] class + * + * @return Stubber object used to stub fluently. + */ fun doCallRealMethod(): Stubber { return Mockito.doCallRealMethod()!! } +/** + * Sets to do nothing, to be applied in reverse stubbing. + * This comes handy is some rare cases, like: + * - stubbing consecutive calls with different behavior + * - when spying a real object, suppress the real implementation of the spied object + * + * 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 + * doNothing().whenever(mock).someMethod() + * ``` + * + * This function acts as an alias for Mockito's [Mockito.doNothing]. + * + * See examples in javadoc for [Mockito] class + * + * @return Stubber object used to stub fluently. + */ fun doNothing(): Stubber { - return Mockito.doNothing()!! + return Mockito.doNothing() } +/** + * Sets to return the given value, to be applied in reverse stubbing. + * + * 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 + * doReturn(10).whenever(mock).someMethod() + * ``` + * + * This function acts as an alias for Mockito's [Mockito.doReturn]. + * + * See examples in javadoc for [Mockito] class + * + * @param value return value for the method/function invocation. + * @return Stubber object used to stub fluently. + */ fun doReturn(value: Any?): Stubber { - return Mockito.doReturn(value)!! + return Mockito.doReturn(value) +} + +/** + * Sets to return the given values on consecutive invocations, to be applied in reverse stubbing. + * + * 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 + * doReturn(10, 20).whenever(mock).someMethod() + * ``` + * + * This function acts as an alias for Mockito's [Mockito.doReturn]. + * + * See examples in javadoc for [Mockito] class + * + * @param value return value for the first method/function invocation. + * @param values return values for the next method/function invocations. + * @return Stubber object used to stub fluently. + */ +fun doReturn(value: Any?, vararg values: Any?): Stubber { + return Mockito.doReturn(value, *values) +} + +/** + * Sets a Throwable to be thrown, to be applied in reverse stubbing. + * + * 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 + * doThrow(IllegalArgumentException()).whenever(mock).someMethod() + * ``` + * + * This function acts as an alias for Mockito's [Mockito.doThrow]. + * + * See examples in javadoc for [Mockito] class + * + * @param throwable to be thrown on the method/function invocation. + * @return Stubber object used to stub fluently. + */ +fun doThrow(throwable: Throwable): Stubber { + return Mockito.doThrow(throwable) } -fun doReturn(toBeReturned: Any?, vararg toBeReturnedNext: Any?): Stubber { - return Mockito.doReturn( - toBeReturned, - *toBeReturnedNext - )!! +/** + * Sets Throwables to be thrown consecutively, to be applied in reverse stubbing. + * + * 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 + * doThrow(RuntimeException(), IOException()).whenever(mock).someMethod() + * ``` + * + * This function acts as an alias for Mockito's [Mockito.doThrow]. + * + * You can specify [throwables] to be thrown for consecutive invocations. + * In that case the last throwable determines the behavior of further consecutive invocations. + * + * See examples in javadoc for [Mockito] class + * + * @param throwable to be thrown on the first method/function invocation. + * @param throwables to be thrown on the next method/function invocations. + * @return Stubber object used to stub fluently. + */ +fun doThrow(throwable: Throwable, vararg throwables: Throwable): Stubber { + return Mockito.doThrow(throwable, *throwables) } -fun doThrow(toBeThrown: KClass): Stubber { - return Mockito.doThrow(toBeThrown.java)!! +/** + * Sets a Throwable types to be thrown, to be applied in reverse stubbing. + * + * 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 + * doThrow(IllegalArgumentException::class).whenever(mock).someMethod() + * ``` + * + * This function acts as an alias for Mockito's [Mockito.doThrow]. + * + * See examples in javadoc for [Mockito] class + * + * @param throwableType to be thrown on the method/function invocation. + * @return Stubber object used to stub fluently. + */ +fun doThrow(throwableType: KClass): Stubber { + return Mockito.doThrow(throwableType.java) } -fun doThrow(vararg toBeThrown: Throwable): Stubber { - return Mockito.doThrow(*toBeThrown)!! +/** + * Sets a Throwable types to be thrown consecutively, to be applied in reverse stubbing. + * + * 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 + * doThrow(IllegalArgumentException::class, NullPointerException::class) + * .whenever(mock) + * .someMethod() + * ``` + * + * This function acts as an alias for Mockito's [Mockito.doThrow]. + * + * You can specify [throwableTypes] to be thrown for consecutive invocations. + * In that case the last throwable type determines the behavior of further consecutive invocations. + * + * See examples in javadoc for [Mockito] class + * + * @param throwableType to be thrown on the first method/function invocation. + * @param throwableTypes to be thrown on the next method/function invocations. + * @return Stubber object used to stub fluently. + */ +fun doThrow(throwableType: KClass, vararg throwableTypes: KClass): Stubber { + return Mockito.doThrow( + throwableType.java, + *throwableTypes.map { it.java }.toTypedArray() + ) } -fun Stubber.whenever(mock: T) = `when`(mock) +/** + * Sets the mock to apply the reverse stubbing on. + * + * 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 + * doReturn(10).whenever(mock).someMethod() + * ``` + * + * This function acts as an alias for Mockito's [Mockito.doReturn]. + * + * See examples in javadoc for [Mockito] class + * + * @param mock the mock to stub the method/function call on. + * @return the mock used to stub the method/function call fluently. + */ +fun Stubber.whenever(mock: T) = `when`(mock)!! + +/** + * Sets the mock and the method call to be stubbed. + * With this version of whenever 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 + * doReturn(10).whenever(mock) { someMethod() } + * ``` + * Warning: Only one method call can be stubbed in the function. Subsequent method calls are ignored! + * + * This function acts as an alias for Mockito's [Mockito.`when`]. + * + * See examples in javadoc for [Mockito] class + * + * @param mock the mock to stub the method/function call on. + * @param methodCall (regular or suspendable) lambda, wrapping the method/function call to be stubbed. + */ +fun Stubber.whenever(mock: T, methodCall: suspend T.() -> Unit) { + whenever(mock).let { + runBlocking { it.methodCall() } + } +} /** - * Alias for when with suspending function + * Sets the mock and the method call to be stubbed. + * With this version of whenever 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 + * doReturn(10).wheneverBlocking(mock) { someMethod() } + * ``` + * Warning: Only one method call can be stubbed in the function. Subsequent method calls are ignored! + * + * This function is an alias for [whenever]. + * + * See examples in javadoc for [Mockito] class * - * Warning: Only one method call can be stubbed in the function. - * Subsequent method calls are ignored! + * @param mock the mock to stub the method/function call on. + * @param methodCall (regular or suspendable) lambda, wrapping the method/function call to be stubbed. */ -fun Stubber.wheneverBlocking(mock: T, f: suspend T.() -> Unit) { - val m = whenever(mock) - runBlocking { m.f() } +@Deprecated("Use whenever(mock) { methodCall() } instead") +fun Stubber.wheneverBlocking(mock: T, methodCall: suspend T.() -> Unit) { + whenever(mock, methodCall) } diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/SuspendableAnswer.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/SuspendableAnswer.kt index 239be2cd..62a96d75 100644 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/SuspendableAnswer.kt +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/SuspendableAnswer.kt @@ -40,7 +40,7 @@ internal class SuspendableAnswer( private val body: suspend (KInvocationOnMock) -> T? ) : Answer { override fun answer(invocation: InvocationOnMock?): T { - //all suspend functions/lambdas has Continuation as the last argument. + //all suspendable functions/lambdas have Continuation as the last argument. //InvocationOnMock does not see last argument val rawInvocation = invocation as InterceptedInvocation val continuation = rawInvocation.rawArguments.last() as Continuation diff --git a/tests/src/test/kotlin/test/BDDMockitoTest.kt b/tests/src/test/kotlin/test/BDDMockitoTest.kt index 457bf406..13be9d53 100644 --- a/tests/src/test/kotlin/test/BDDMockitoTest.kt +++ b/tests/src/test/kotlin/test/BDDMockitoTest.kt @@ -120,7 +120,7 @@ class BDDMockitoTest { fun then() { /* Given */ val mock = mock() - whenever(mock.stringResult()).thenReturn("Test") + whenever { mock.stringResult() }.thenReturn("Test") /* When */ mock.stringResult() diff --git a/tests/src/test/kotlin/test/Classes.kt b/tests/src/test/kotlin/test/Classes.kt index ca06afd6..4ae7be05 100644 --- a/tests/src/test/kotlin/test/Classes.kt +++ b/tests/src/test/kotlin/test/Classes.kt @@ -154,6 +154,7 @@ abstract class NonThrowingConstructorWithArgument { interface GenericMethods { fun genericMethod(): T fun nullableReturnType(): T? + suspend fun suspendableGenericMethod(): T } class ThrowableClass(cause: Throwable) : Throwable(cause) diff --git a/tests/src/test/kotlin/test/CoroutinesOngoingStubbingTest.kt b/tests/src/test/kotlin/test/CoroutinesOngoingStubbingTest.kt index f3ee3067..2dd9a3a5 100644 --- a/tests/src/test/kotlin/test/CoroutinesOngoingStubbingTest.kt +++ b/tests/src/test/kotlin/test/CoroutinesOngoingStubbingTest.kt @@ -21,7 +21,7 @@ class CoroutinesOngoingStubbingTest { fun `should stub suspendable function call`() { /* Given */ val mock = mock { - onBlocking { stringResult() } doReturn "A" + on { stringResult() } doReturn "A" } /* When */ @@ -35,7 +35,7 @@ class CoroutinesOngoingStubbingTest { fun `should stub suspendable function call within a coroutine scope`() = runTest { /* Given */ val mock = mock { - onBlocking { stringResult() } doReturn "A" + on { stringResult() } doReturn "A" } /* When */ @@ -49,7 +49,7 @@ class CoroutinesOngoingStubbingTest { fun `should stub consecutive suspendable function calls`() { /* Given */ val mock = mock { - onBlocking { stringResult() }.doReturn("A", "B", "C") + on { stringResult() }.doReturn("A", "B", "C") } /* When */ @@ -67,7 +67,7 @@ class CoroutinesOngoingStubbingTest { fun `should stub consecutive suspendable function calls by a list of answers`() { /* Given */ val mock = mock { - onBlocking { stringResult() } doReturnConsecutively listOf("A", "B", "C") + on { stringResult() } doReturnConsecutively listOf("A", "B", "C") } /* When */ @@ -99,7 +99,7 @@ class CoroutinesOngoingStubbingTest { fun `should stub builder method returning mock itself via answer`() { /* Given */ val mock = mock { - onBlocking { builderMethod() } doAnswer Mockito.RETURNS_SELF + on { builderMethod() } doAnswer Mockito.RETURNS_SELF } /* When */ @@ -113,7 +113,7 @@ class CoroutinesOngoingStubbingTest { fun `should stub builder method returning mock itself`() { /* Given */ val mock = mock { mock -> - onBlocking { builderMethod() } doReturn mock + on { builderMethod() } doReturn mock } /* When */ @@ -127,7 +127,7 @@ class CoroutinesOngoingStubbingTest { fun `should stub suspendable function call with nullable result`() { /* Given */ val mock = mock { - onBlocking { nullableStringResult() } doReturn "Test" + on { nullableStringResult() } doReturn "Test" } /* When */ @@ -141,7 +141,7 @@ class CoroutinesOngoingStubbingTest { fun `should throw exception instance on suspendable function call`() { /* Given */ val mock = mock { - onBlocking { builderMethod() } doThrow IllegalArgumentException() + on { builderMethod() } doThrow IllegalArgumentException() } /* When, Then */ @@ -156,7 +156,7 @@ class CoroutinesOngoingStubbingTest { fun `should throw exception class on suspendable function call`() { /* Given */ val mock = mock { - onBlocking { builderMethod() } doThrow IllegalArgumentException::class + on { builderMethod() } doThrow IllegalArgumentException::class } /* When, Then */ @@ -171,7 +171,7 @@ class CoroutinesOngoingStubbingTest { fun `should throw exception instances on consecutive suspendable function calls`() { /* Given */ val mock = mock { - onBlocking { builderMethod() }.doThrow( + on { builderMethod() }.doThrow( IllegalArgumentException(), UnsupportedOperationException() ) @@ -192,7 +192,7 @@ class CoroutinesOngoingStubbingTest { fun `should throw exception classes on consecutive suspendable function calls`() { /* Given */ val mock = mock { - onBlocking { builderMethod() }.doThrow( + on { builderMethod() }.doThrow( IllegalArgumentException::class, UnsupportedOperationException::class ) @@ -214,7 +214,7 @@ class CoroutinesOngoingStubbingTest { /* Given */ val answer: Answer = Answer { "result" } val mock = mock { - onBlocking { stringResult() } doAnswer answer + on { stringResult() } doAnswer answer } /* When */ @@ -229,7 +229,7 @@ class CoroutinesOngoingStubbingTest { fun `should stub suspendable function call with result from lambda`() { /* Given */ val mock = mock { - onBlocking { stringResult() } doAnswer { "result" } + on { stringResult() } doAnswer { "result" } } /* When */ @@ -243,7 +243,7 @@ class CoroutinesOngoingStubbingTest { fun `should stub suspendable function call with result from lambda with argument`() { /* Given */ val mock = mock { - onBlocking { stringResult(any()) } doAnswer { "${it.arguments[0]}-result" } + on { stringResult(any()) } doAnswer { "${it.arguments[0]}-result" } } /* When */ @@ -257,7 +257,7 @@ class CoroutinesOngoingStubbingTest { fun `should stub suspendable function call with result from lambda with deconstructed argument`() { /* Given */ val mock = mock { - onBlocking { stringResult(any()) } doAnswer { (s: String) -> "$s-result" } + on { stringResult(any()) } doAnswer { (s: String) -> "$s-result" } } /* When */ @@ -271,7 +271,7 @@ class CoroutinesOngoingStubbingTest { fun `should stub suspendable function call with result from lambda with deconstructed arguments`() { /* Given */ val mock = mock { - onBlocking { stringResult(any(), any()) } doAnswer { (a: String, b: String) -> + on { stringResult(any(), any()) } doAnswer { (a: String, b: String) -> "$a + $b" } } diff --git a/tests/src/test/kotlin/test/CoroutinesTest.kt b/tests/src/test/kotlin/test/CoroutinesTest.kt index 044402d8..1612b4be 100644 --- a/tests/src/test/kotlin/test/CoroutinesTest.kt +++ b/tests/src/test/kotlin/test/CoroutinesTest.kt @@ -23,7 +23,6 @@ import org.mockito.kotlin.spy import org.mockito.kotlin.verify import org.mockito.kotlin.verifyBlocking import org.mockito.kotlin.whenever -import org.mockito.kotlin.wheneverBlocking import java.util.* class CoroutinesTest { @@ -32,7 +31,7 @@ class CoroutinesTest { fun stubbingSuspending() { /* Given */ val m = mock { - onBlocking { intResult() } doReturn 42 + on { intResult() } doReturn 42 } /* When */ @@ -46,7 +45,7 @@ class CoroutinesTest { fun stubbingSuspending_usingSuspendingFunction() { /* Given */ val m = mock { - onBlocking { intResult() } doReturn runBlocking { Open().intResult(42) } + on { intResult() } doReturn runBlocking { Open().intResult(42) } } /* When */ @@ -60,7 +59,7 @@ class CoroutinesTest { fun stubbingSuspending_runBlocking() = runBlocking { /* Given */ val mock = mock { - onBlocking { intResult() } doReturn 42 + on { intResult() } doReturn 42 } /* When */ @@ -74,7 +73,7 @@ class CoroutinesTest { fun stubbingSuspending_wheneverBlocking() { /* Given */ val mock: SuspendFunctions = mock() - wheneverBlocking { mock.intResult() } + whenever { mock.intResult() } .doReturn(42) /* When */ @@ -88,10 +87,7 @@ class CoroutinesTest { fun stubbingSuspending_doReturn() { /* Given */ val spy = spy(Open()) - doReturn(10) - .wheneverBlocking(spy) { - delayedIntResult() - } + doReturn(10).whenever(spy) { delayedIntResult() } /* When */ val result = runBlocking { spy.delayedIntResult() } @@ -104,7 +100,7 @@ class CoroutinesTest { fun stubbingNonSuspending() { /* Given */ val mock = mock { - onBlocking { intResult() } doReturn 42 + on { intResult() } doReturn 42 } /* When */ @@ -118,7 +114,7 @@ class CoroutinesTest { fun stubbingNonSuspending_runBlocking() = runBlocking { /* Given */ val mock = mock { - onBlocking { intResult() } doReturn 42 + on { intResult() } doReturn 42 } /* When */ @@ -207,7 +203,7 @@ class CoroutinesTest { fun answerWithSuspendFunction() = runBlocking { val mock: SuspendFunctions = mock() - whenever(mock.intResult(any())).doSuspendableAnswer { + whenever { mock.intResult(any()) } doSuspendableAnswer { withContext(Dispatchers.Default) { it.getArgument(0) } } @@ -217,7 +213,7 @@ class CoroutinesTest { @Test fun inplaceAnswerWithSuspendFunction() = runBlocking { val mock: SuspendFunctions = mock { - onBlocking { intResult(any()) } doSuspendableAnswer { + on { intResult(any()) } doSuspendableAnswer { withContext(Dispatchers.Default) { it.getArgument(0) } } } @@ -229,7 +225,7 @@ class CoroutinesTest { fun callFromSuspendFunction() = runBlocking { val mock: SuspendFunctions = mock() - whenever(mock.intResult(any())).doSuspendableAnswer { + whenever { mock.intResult(any()) } doSuspendableAnswer { withContext(Dispatchers.Default) { it.getArgument(0) } } @@ -247,7 +243,7 @@ class CoroutinesTest { fun callFromActor() = runBlocking { val mock: SuspendFunctions = mock() - whenever(mock.intResult(any())).doSuspendableAnswer { + whenever { mock.intResult(any()) } doSuspendableAnswer { withContext(Dispatchers.Default) { it.getArgument(0) } } @@ -269,7 +265,7 @@ class CoroutinesTest { fun answerWithSuspendFunctionWithoutArgs() = runBlocking { val mock: SuspendFunctions = mock() - whenever(mock.intResult()).doSuspendableAnswer { + whenever { mock.intResult() } doSuspendableAnswer { withContext(Dispatchers.Default) { 42 } } @@ -280,7 +276,7 @@ class CoroutinesTest { fun answerWithSuspendFunctionWithDestructuredArgs() = runBlocking { val mock: SuspendFunctions = mock() - whenever(mock.intResult(any())).doSuspendableAnswer { (i: Int) -> + whenever { mock.intResult(any()) } doSuspendableAnswer { (i: Int) -> withContext(Dispatchers.Default) { i } } @@ -293,7 +289,7 @@ class CoroutinesTest { val job = Job() - whenever(mock.intResult()).doSuspendableAnswer { + whenever { mock.intResult() } doSuspendableAnswer { job.join() 5 } diff --git a/tests/src/test/kotlin/test/MatchersTest.kt b/tests/src/test/kotlin/test/MatchersTest.kt index c913e4b0..1ced48ba 100644 --- a/tests/src/test/kotlin/test/MatchersTest.kt +++ b/tests/src/test/kotlin/test/MatchersTest.kt @@ -238,7 +238,7 @@ class MatchersTest : TestBase() { @Test fun anyVarargMatching() { mock().apply { - whenever(varargBooleanResult(anyVararg())).thenReturn(true) + whenever { varargBooleanResult(anyVararg()) }.thenReturn(true) expect(varargBooleanResult()).toBe(true) } } @@ -727,7 +727,7 @@ class MatchersTest : TestBase() { val matcher = VarargAnyMatcher({ "b" == it }, String::class.java, true, false) /* When */ - whenever(t.varargBooleanResult(argThat(matcher))).thenAnswer(matcher) + whenever { t.varargBooleanResult(argThat(matcher)) }.thenAnswer(matcher) /* Then */ expect(t.varargBooleanResult("a", "b", "c")).toBe(true) @@ -741,7 +741,7 @@ class MatchersTest : TestBase() { val matcher = VarargAnyMatcher({ "d" == it }, String::class.java, true, false) /* When */ - whenever(t.varargBooleanResult(argThat(matcher))).thenAnswer(matcher) + whenever { t.varargBooleanResult(argThat(matcher)) }.thenAnswer(matcher) /* Then */ expect(t.varargBooleanResult("a", "b", "c")).toBe(false) diff --git a/tests/src/test/kotlin/test/MockingTest.kt b/tests/src/test/kotlin/test/MockingTest.kt index bd751302..f6e86e5c 100644 --- a/tests/src/test/kotlin/test/MockingTest.kt +++ b/tests/src/test/kotlin/test/MockingTest.kt @@ -76,7 +76,7 @@ class MockingTest : TestBase() { @Test fun deepStubs() { val cal: Calendar = mock(defaultAnswer = Mockito.RETURNS_DEEP_STUBS) - whenever(cal.time.time).thenReturn(123L) + whenever { cal.time.time }.thenReturn(123L) expect(cal.time.time).toBe(123L) } @@ -97,10 +97,10 @@ class MockingTest : TestBase() { @Test fun testMockStubbing_normalOverridesLambda() { /* Given */ - val mock = mock() { - on { stringResult() }.doReturn("A") + val mock = mock { + on { stringResult() } doReturn "A" } - whenever(mock.stringResult()).thenReturn("B") + whenever { mock.stringResult() }.thenReturn("B") /* When */ val result = mock.stringResult() @@ -280,7 +280,7 @@ class MockingTest : TestBase() { fun mockSuspendFunction_withClosedBooleanReturn_name() = runTest { /* Given */ val mock = mock(name = "myName") { - onBlocking { closedBooleanResult(any()) } doReturn true + on { closedBooleanResult(any()) } doReturn true } /* When */ @@ -294,7 +294,7 @@ class MockingTest : TestBase() { fun mockSuspendFunction_withClassClosedBooleanReturn_name() = runTest { /* Given */ val mock = mock(name = "myName") { - onBlocking { classClosedBooleanResult(any()) } doReturn true + on { classClosedBooleanResult(any()) } doReturn true } /* When */ @@ -412,7 +412,7 @@ class MockingTest : TestBase() { @Test fun mockConstruction_withInitializer() { mockConstruction { mock, _ -> - whenever(mock.stringResult()).thenReturn("Hello") + whenever { mock.stringResult() }.thenReturn("Hello") }.use { val open = Open() diff --git a/tests/src/test/kotlin/test/OngoingStubbingTest.kt b/tests/src/test/kotlin/test/OngoingStubbingTest.kt index 0732fcce..11ececf7 100644 --- a/tests/src/test/kotlin/test/OngoingStubbingTest.kt +++ b/tests/src/test/kotlin/test/OngoingStubbingTest.kt @@ -2,6 +2,7 @@ package test import com.nhaarman.expect.expect import com.nhaarman.expect.expectErrorWithMessage +import kotlinx.coroutines.runBlocking import org.junit.Assume.assumeFalse import org.junit.Test import org.mockito.Mockito @@ -125,36 +126,48 @@ class OngoingStubbingTest : TestBase() { assertThrows { mock.builderMethod() } + // any consecutive call should throw the last specified exception + assertThrows { + mock.builderMethod() + } } @Test - fun `should throw exception class on function call`() { + fun `should throw exception instances on consecutive function calls`() { /* Given */ val mock = mock { - on { builderMethod() } doThrow IllegalArgumentException::class + on { builderMethod() }.doThrow( + IllegalArgumentException(), + UnsupportedOperationException() + ) } /* When, Then */ assertThrows { mock.builderMethod() } + assertThrows { + mock.builderMethod() + } + // any consecutive call should throw the last specified exception + assertThrows { + mock.builderMethod() + } } @Test - fun `should throw exception instances on consecutive function calls`() { + fun `should throw exception class on function call`() { /* Given */ val mock = mock { - on { builderMethod() }.doThrow( - IllegalArgumentException(), - UnsupportedOperationException() - ) + on { builderMethod() } doThrow IllegalArgumentException::class } /* When, Then */ assertThrows { mock.builderMethod() } - assertThrows { + // any consecutive call should throw the last specified exception + assertThrows { mock.builderMethod() } } @@ -176,6 +189,10 @@ class OngoingStubbingTest : TestBase() { assertThrows { mock.builderMethod() } + // any consecutive call should throw the last specified exception + assertThrows { + mock.builderMethod() + } } @Test @@ -252,10 +269,10 @@ class OngoingStubbingTest : TestBase() { } @Test - fun `should stub function call with integer result`() { + fun `should stub generics function call with explicit generics type`() { /* Given */ val mock = mock> { - onGeneric { genericMethod() } doReturn 2 + onGeneric({ genericMethod() }, Int::class) doReturn 2 } /* Then */ @@ -263,12 +280,31 @@ class OngoingStubbingTest : TestBase() { } @Test - fun `should stub nullable function call with string result`() { - val m = mock> { - onGeneric { nullableReturnType() } doReturn "Test" + fun `should stub nullable generics function call with string result`() { + /* Given */ + val mock = mock> { + on { nullableReturnType() } doReturn "Test" } - expect(m.nullableReturnType()).toBe("Test") + /* When */ + val result = mock.nullableReturnType() + + /* Then */ + expect(result).toBe("Test") + } + + @Test + fun `should stub suspendable generics function call with integer result`() { + /* Given */ + val mock = mock> { + on { suspendableGenericMethod() } doReturn 2 + } + + /* When */ + val result = runBlocking { mock.suspendableGenericMethod() } + + /* Then */ + expect(result).toBe(2) } @Test @@ -323,7 +359,7 @@ class OngoingStubbingTest : TestBase() { val valueClassA = ValueClass("A") val valueClassB = ValueClass("B") val mock = mock { - on { valueClassResult() }.doReturnConsecutively(listOf(valueClassA, valueClassB)) + on { valueClassResult() } doReturnConsecutively listOf(valueClassA, valueClassB) } /* When */ diff --git a/tests/src/test/kotlin/test/StubberTest.kt b/tests/src/test/kotlin/test/StubberTest.kt index 5144cb7e..0c7a4c87 100644 --- a/tests/src/test/kotlin/test/StubberTest.kt +++ b/tests/src/test/kotlin/test/StubberTest.kt @@ -1,14 +1,23 @@ package test import com.nhaarman.expect.expect -import com.nhaarman.expect.expectErrorWithMessage +import kotlinx.coroutines.runBlocking +import kotlinx.coroutines.test.runTest +import org.junit.Assert.assertEquals import org.junit.Test -import org.mockito.kotlin.* +import org.mockito.kotlin.doAnswer +import org.mockito.kotlin.doCallRealMethod +import org.mockito.kotlin.doNothing +import org.mockito.kotlin.doReturn +import org.mockito.kotlin.doThrow +import org.mockito.kotlin.mock +import org.mockito.kotlin.spy +import org.mockito.kotlin.whenever class StubberTest : TestBase() { @Test - fun testDoAnswer() { + fun `should stub function call with result from lambda`() { val mock = mock() doAnswer { "Test" } @@ -19,7 +28,7 @@ class StubberTest : TestBase() { } @Test - fun testDoCallRealMethod() { + fun `should stub function call to call real method implementation`() { val mock = mock() doReturn("Test").whenever(mock).stringResult() @@ -29,7 +38,7 @@ class StubberTest : TestBase() { } @Test - fun testDoNothing() { + fun `should stub function call to do nothing in the spied instance`() { val spy = spy(Open()) val array = intArrayOf(3) @@ -40,7 +49,7 @@ class StubberTest : TestBase() { } @Test - fun testDoReturnValue() { + fun `should stub function call with fixed return value`() { val mock = mock() doReturn("test").whenever(mock).stringResult() @@ -49,7 +58,17 @@ class StubberTest : TestBase() { } @Test - fun testDoReturnNullValue() { + fun `should stub function call with consecutive fixed return values`() { + val mock = mock() + + doReturn("test", "test2").whenever(mock).stringResult() + + expect(mock.stringResult()).toBe("test") + expect(mock.stringResult()).toBe("test2") + } + + @Test + fun `should stub function call with null return value`() { val mock = mock() doReturn(null).whenever(mock).stringResult() @@ -58,7 +77,7 @@ class StubberTest : TestBase() { } @Test - fun testDoReturnNullValues() { + fun `should stub function call with consecutive null return values`() { val mock = mock() doReturn(null, null).whenever(mock).stringResult() @@ -68,41 +87,96 @@ class StubberTest : TestBase() { } @Test - fun testDoReturnValues() { - val mock = mock() + fun `should stub function call to throw exception instance`() { + val mock = mock() - doReturn("test", "test2").whenever(mock).stringResult() + doThrow(IllegalStateException("test")).whenever(mock).go() - expect(mock.stringResult()).toBe("test") - expect(mock.stringResult()).toBe("test2") + val exception: IllegalStateException = assertThrows { + mock.go() + } + assertEquals("test", exception.message) + // any consecutive call should throw the last specified exception + assertThrows { + mock.go() + } + } + + @Test + fun `should stub function call to throw exception instances consecutively`() { + val mock = mock() + + doThrow( + IllegalStateException("test"), + NullPointerException("test2") + ).whenever(mock).go() + + val first: IllegalStateException = assertThrows { + mock.go() + } + assertEquals("test", first.message) + val second: NullPointerException = assertThrows { + mock.go() + } + assertEquals("test2", second.message) + // any consecutive call should throw the last specified exception + assertThrows { + mock.go() + } } @Test - fun testDoThrowClass() { + fun `should stub function call to throw exception class`() { val mock = mock() doThrow(IllegalStateException::class).whenever(mock).go() - try { + assertThrows { + mock.go() + } + // any consecutive call should throw the last specified exception + assertThrows { mock.go() - throw AssertionError("Call should have thrown.") - } catch (_: IllegalStateException) { } } @Test - fun testDoThrow() { + fun `should stub function call to throw exception classes consecutively`() { val mock = mock() - doThrow(IllegalStateException("test")).whenever(mock).go() + doThrow( + IllegalStateException::class, + NullPointerException::class + ).whenever(mock).go() - expectErrorWithMessage("test").on { + assertThrows { + mock.go() + } + assertThrows { mock.go() } + // any consecutive call should throw the last specified exception + assertThrows { + mock.go() + } + } + + @Test + fun `should stub suspendable function call in reverse manner, with on() as part of mock creation`() = runTest{ + /* Given */ + val mock = mock { + doReturn( "A") on { stringResult() } + } + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("A") } @Test - fun testStubberOnBlockExtension() { + fun `should stub synchronous function call in reverse manner, with on() as part of mock creation`() = runTest{ val mock = mock { doReturn("Test").on { stringResult() } } diff --git a/tests/src/test/kotlin/test/StubbingTest.kt b/tests/src/test/kotlin/test/StubbingTest.kt index 77a1c386..357f98ee 100644 --- a/tests/src/test/kotlin/test/StubbingTest.kt +++ b/tests/src/test/kotlin/test/StubbingTest.kt @@ -64,6 +64,18 @@ class StubbingTest { expect(result).toBe("result") } + @Test + fun `should stub already existing mock, using whenever function`() { + val mock = mock() + whenever { mock.stringResult() } doReturn "result" + + /* When */ + val result = mock.stringResult() + + /* Then */ + expect(result).toBe("result") + } + @Test fun `should override default stub of mock`() { /* Given mock with stub */ @@ -135,7 +147,7 @@ class StubbingTest { fun `should throw when stubbing is incomplete`() { /* Given */ val mock = mock() - whenever(mock.stringResult()) + whenever { mock.stringResult() } /* When */ try { @@ -143,14 +155,13 @@ class StubbingTest { } catch (e: UnfinishedStubbingException) { /* Then */ expect(e.message).toContain("Unfinished stubbing detected here:") - expect(e.message).toContain("-> at test.StubbingTest.should throw when stubbing is incomplete") } } @Test fun `should stub sync method call in reverse manner as part of mock creation`() { val mock = mock { - doReturn("A").on { stringResult() } + doReturn("A") on { stringResult() } } expect(mock.stringResult()).toBe("A") @@ -160,7 +171,7 @@ class StubbingTest { fun `should stub sync method call, with whenever`() { /* Given */ val mock = mock() - whenever(mock.stringResult()).doReturn("A") + whenever { mock.stringResult() } doReturn "A" /* When */ val result = mock.stringResult() @@ -213,10 +224,10 @@ class StubbingTest { } @Test - fun `should stub suspendable function call, with wheneverBlocking as part of mock creation`() { + fun `should stub suspendable function call, with whenever as part of mock creation`() { /* Given */ val mock = mock { mock -> - wheneverBlocking { mock.stringResult() } doReturn "A" + whenever { mock.stringResult() } doReturn "A" } /* When */ @@ -227,10 +238,10 @@ class StubbingTest { } @Test - fun `should stub suspendable function call in reverse manner, with wheneverBlocking as part of mock creation`() { + fun `should stub sync method call in reverse manner, with whenever and lambda as part of mock creation`() { /* Given */ - val mock = mock { mock -> - doReturn( "A").wheneverBlocking(mock) { mock.stringResult() } + val mock = mock { mock -> + doReturn( "A").whenever(mock) { stringResult() } } /* When */ @@ -241,9 +252,37 @@ class StubbingTest { } @Test - fun `should stub suspendable function call in reverse manner, with whenever as part of mock creation`() = runTest{ + fun `should stub suspendable function call in reverse manner with on() as part of mock creation`() { /* Given */ - val mock = mock { mock -> + val mock = mock { + doReturn( "A") on { stringResult() } + } + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub suspendable function call in reverse manner, with whenever() and lambda as part of mock creation`() { + /* Given */ + val mock = mock { mock -> + doReturn( "A").whenever(mock) { stringResult() } + } + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("A") + } + + @Test + fun `should stub suspendable function call in reverse manner, with whenever() as part of mock creation`() = runTest{ + /* Given */ + val mock = mock { doReturn( "A").whenever(mock).stringResult() } @@ -258,7 +297,7 @@ class StubbingTest { fun `should stub sync function call within a suspendable lambda`() { /* Given */ val mock = mock { - onBlocking { stringResult() } doReturn "A" + on { stringResult() } doReturn "A" } /* When */ @@ -269,10 +308,10 @@ class StubbingTest { } @Test - fun `should stub sync function call within a suspendable lambda of wheneverBlocking`() { + fun `should stub sync function call within a suspendable lambda of whenever`() { /* Given */ val mock = mock() - wheneverBlocking { mock.stringResult() } doReturn "A" + whenever { mock.stringResult() } doReturn "A" /* When */ val result = mock.stringResult() @@ -282,7 +321,7 @@ class StubbingTest { } @Test - fun `should stub suspend function call with a call to the sync version of whenever`() = runTest { + fun `should stub suspendable function call with a call to the sync version of whenever`() = runTest { /* Given */ val mock = mock() whenever (mock.stringResult()) doReturn "A" @@ -295,10 +334,10 @@ class StubbingTest { } @Test - fun `should provide backwards support to stub suspendable function call with 'whenever' method`() = runTest { + fun `should support to stub suspendable function call with synchronous 'whenever' method`() = runTest { /* Given */ val mock = mock() - whenever(mock.stringResult()).doSuspendableAnswer { + whenever(mock.stringResult()) doSuspendableAnswer { delay(0) "A" } @@ -309,4 +348,63 @@ class StubbingTest { /* Then */ expect(result).toBe("A") } + + @Test + @Suppress("DEPRECATION") + fun `should provide backwards compatibility support to stub suspendable function call with 'wheneverBlocking' method`() { + /* Given */ + val mock = mock() + wheneverBlocking { mock.stringResult() } doReturn "A" + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("A") + } + + @Test + @Suppress("DEPRECATION") + fun `should provide backwards compatibility support to stub suspendable function call in reverse manner, with wheneverBlocking`() { + /* Given */ + val mock = mock { mock -> + doReturn( "A").wheneverBlocking(mock) { stringResult() } + } + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("A") + } + + @Test + @Suppress("DEPRECATION") + fun `should provide backwards compatibility support to stub suspendable function call with 'onBlocking' method`() { + /* Given */ + val mock = mock { + onBlocking { mock.stringResult() } doReturn "A" + } + + /* When */ + val result = runBlocking { mock.stringResult() } + + /* Then */ + expect(result).toBe("A") + } + + @Test + @Suppress("DEPRECATION") + fun `should provide backwards compatibility support to stub generics function calls with integer result`() { + /* Given */ + val mock = mock> { + onGeneric { genericMethod() } doReturn 2 + } + + /* When */ + val result = mock.genericMethod() + + /* Then */ + expect(result).toBe(2) + } } diff --git a/tests/src/test/kotlin/test/inline/UsingMockMakerInlineTest.kt b/tests/src/test/kotlin/test/inline/UsingMockMakerInlineTest.kt index b03d77ea..d6bb6192 100644 --- a/tests/src/test/kotlin/test/inline/UsingMockMakerInlineTest.kt +++ b/tests/src/test/kotlin/test/inline/UsingMockMakerInlineTest.kt @@ -74,7 +74,7 @@ class UsingMockMakerInlineTest { fun mockClosedFunction_mockStubbing() { /* Given */ val mock = mock { - on { doSomethingElse(any()) } doReturn (BigInteger.ONE) + on { doSomethingElse(any()) } doReturn BigInteger.ONE } /* When */ @@ -88,7 +88,7 @@ class UsingMockMakerInlineTest { fun mockClosedFunction_whenever() { /* Given */ val mock = mock() - whenever(mock.doSomethingElse(any())).doReturn(BigInteger.ONE) + whenever { mock.doSomethingElse(any()) } doReturn BigInteger.ONE /* When */ val result = mock.doSomethingElse(BigInteger.TEN)