diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
index 69f8a38d..704de466 100644
--- a/.idea/codeStyles/Project.xml
+++ b/.idea/codeStyles/Project.xml
@@ -31,6 +31,30 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
@@ -61,10 +85,9 @@
+
+
-
-
-
@@ -95,7 +118,6 @@
-
@@ -261,13 +283,6 @@
-
-
-
-
-
-
-
diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/CoroutinesOngoingStubbing.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/CoroutinesOngoingStubbing.kt
new file mode 100644
index 00000000..e87b2dac
--- /dev/null
+++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/CoroutinesOngoingStubbing.kt
@@ -0,0 +1,213 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2018 Niek Haarman
+ * Copyright (c) 2007 Mockito contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.mockito.kotlin
+
+import kotlinx.coroutines.delay
+import org.mockito.internal.stubbing.answers.Returns
+import org.mockito.kotlin.internal.SuspendableAnswer
+import org.mockito.kotlin.internal.lastInvocationMethodIsSuspend
+import org.mockito.stubbing.Answer
+import org.mockito.stubbing.OngoingStubbing
+import kotlin.reflect.KClass
+
+class CoroutinesOngoingStubbing(val mockitoOngoingStubbing: OngoingStubbing) {
+ /**
+ * Sets a return value to be returned when the method is called.
+ *
+ * Alias for [thenReturn].
+ */
+ infix fun doReturn(t: T): CoroutinesOngoingStubbing {
+ return thenReturn(t)
+ }
+
+ /**
+ * Sets a return value to be returned when the method is called.
+ */
+ @Suppress("UNCHECKED_CAST")
+ infix fun thenReturn(t: T): CoroutinesOngoingStubbing {
+ return thenAnswer(Returns(t))
+ }
+
+ /**
+ * Sets an answer for the suspendable function using a suspendable lambda.
+ *
+ * Alias for [thenAnswer].
+ */
+ infix fun doAnswer(answer: suspend (KInvocationOnMock) -> T?): CoroutinesOngoingStubbing {
+ return thenAnswer(answer)
+ }
+
+ /**
+ * Sets an answer for the suspendable function using a suspendable lambda.
+ *
+ * Alias for [thenAnswer].
+ *
+ * This deprecated method was added for backwards compatibility.
+ */
+ @Deprecated(
+ " use doAnswer() or thenAnswer() instead.",
+ level = DeprecationLevel.WARNING
+ )
+ infix fun doSuspendableAnswer(answer: suspend (KInvocationOnMock) -> T?): CoroutinesOngoingStubbing {
+ return thenAnswer(answer)
+ }
+
+ /**
+ * Sets an answer for the suspendable function using a suspendable lambda.
+ */
+ infix fun thenAnswer(answer: suspend (KInvocationOnMock) -> T?): CoroutinesOngoingStubbing {
+ return thenAnswer(SuspendableAnswer(answer))
+ }
+
+ /**
+ * Sets an answer for the suspendable function by wrapping a non-suspendable Mockito Answer.
+ *
+ * Alias for [thenAnswer].
+ */
+ infix fun doAnswer(answer: Answer<*>): CoroutinesOngoingStubbing {
+ return thenAnswer(answer)
+ }
+
+ /**
+ * Sets an answer for the suspendable function by wrapping a non-suspendable Mockito Answer.
+ */
+ infix fun thenAnswer(answer: Answer<*>): CoroutinesOngoingStubbing {
+ return if (mockitoOngoingStubbing.lastInvocationMethodIsSuspend ?: false) {
+ answer.wrapAsSuspendableAnswer()
+ } else {
+ answer // to support stubbing a sync method call while using wheneverBlocking()/onBlocking()
+ }.let {
+ CoroutinesOngoingStubbing(mockitoOngoingStubbing.thenAnswer(it))
+ }
+ }
+
+ private fun Answer<*>.wrapAsSuspendableAnswer(): Answer =
+ (this as? SuspendableAnswer<*>) ?: SuspendableAnswer(
+ { invocation ->
+ suspendToEnforceProperValueBoxing()
+ this.answer(invocation)
+ }
+ )
+
+ private suspend fun suspendToEnforceProperValueBoxing() {
+ // delaying for 1 ms, forces a suspension to happen.
+ // This (somehow) ensures that value class instances will be properly boxed when the
+ // answer is yielded by the mock
+ delay(1)
+ }
+
+ /**
+ * Sets consecutive return values to be returned when the method is called.
+ *
+ * Alias for [OngoingStubbing.thenReturn].
+ */
+ fun doReturn(t: T, vararg ts: T): CoroutinesOngoingStubbing {
+ return thenReturn(listOf(t, *ts))
+ }
+
+ /**
+ * Sets consecutive return values to be returned when the method is called.
+ */
+ fun doReturnConsecutively(vararg ts: T): CoroutinesOngoingStubbing {
+ return doReturnConsecutively(listOf(*ts))
+ }
+
+ /**
+ * Sets consecutive return values to be returned when the method is called.
+ */
+ infix fun doReturnConsecutively(ts: List): CoroutinesOngoingStubbing {
+ return thenReturn(ts)
+ }
+
+ /**
+ * Sets Throwable objects to be thrown when the method is called.
+ *
+ * Alias for [OngoingStubbing.thenThrow].
+ */
+ infix fun doThrow(t: Throwable): CoroutinesOngoingStubbing {
+ return CoroutinesOngoingStubbing(
+ mockitoOngoingStubbing.thenThrow(t)
+ )
+ }
+
+ /**
+ * Sets Throwable objects to be thrown when the method is called.
+ *
+ * Alias for [OngoingStubbing.doThrow].
+ */
+ fun doThrow(
+ t: Throwable,
+ vararg ts: Throwable
+ ): CoroutinesOngoingStubbing {
+ return CoroutinesOngoingStubbing(
+ mockitoOngoingStubbing.thenThrow(t, *ts)
+ )
+ }
+
+ /**
+ * Sets a Throwable type to be thrown when the method is called.
+ */
+ infix fun doThrow(t: KClass): CoroutinesOngoingStubbing {
+ return CoroutinesOngoingStubbing(
+ mockitoOngoingStubbing.thenThrow(t.java)
+ )
+ }
+
+ /**
+ * Sets Throwable classes to be thrown when the method is called.
+ */
+ fun doThrow(
+ t: KClass,
+ vararg ts: KClass
+ ): CoroutinesOngoingStubbing {
+ return CoroutinesOngoingStubbing(
+ mockitoOngoingStubbing.thenThrow(
+ t.java,
+ *ts.map { it.java }.toTypedArray()
+ )
+ )
+ }
+
+ private fun thenReturn(values: List): CoroutinesOngoingStubbing {
+ return thenAnswer(
+ values.map { value ->
+ SuspendableAnswer(
+ {
+ suspendToEnforceProperValueBoxing()
+ value
+ }
+ )
+ }
+ )
+ }
+
+ private fun thenAnswer(answers: List>): CoroutinesOngoingStubbing {
+ return CoroutinesOngoingStubbing(
+ answers
+ .fold(mockitoOngoingStubbing) { stubbing, answer -> stubbing.thenAnswer(answer) }
+ )
+ }
+}
diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KInvocationOnMock.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KInvocationOnMock.kt
index b433b761..66f13d64 100644
--- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KInvocationOnMock.kt
+++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KInvocationOnMock.kt
@@ -28,7 +28,7 @@ package org.mockito.kotlin
import org.mockito.invocation.InvocationOnMock
class KInvocationOnMock(
- private val invocationOnMock: InvocationOnMock
+ val invocationOnMock: InvocationOnMock
) : InvocationOnMock by invocationOnMock {
operator fun component1(): T = invocationOnMock.getArgument(0)
@@ -36,4 +36,58 @@ class KInvocationOnMock(
operator fun component3(): T = invocationOnMock.getArgument(2)
operator fun component4(): T = invocationOnMock.getArgument(3)
operator fun component5(): T = invocationOnMock.getArgument(4)
+
+ /**
+ * The first argument.
+ * @throws IndexOutOfBoundsException if the argument is not available.
+ */
+ inline fun first(): T = invocationOnMock.getArgument(0)
+
+ /**
+ * The second argument.
+ * @throws IndexOutOfBoundsException if the argument is not available.
+ */
+ inline fun second(): T = invocationOnMock.getArgument(1)
+
+ /**
+ * The third argument.
+ * @throws IndexOutOfBoundsException if the argument is not available.
+ */
+ inline fun third(): T = invocationOnMock.getArgument(2)
+
+ /**
+ * The fourth argument.
+ * @throws IndexOutOfBoundsException if the argument is not available.
+ */
+ inline fun fourth(): T = invocationOnMock.getArgument(3)
+
+ /**
+ * The fifth argument.
+ * @throws IndexOutOfBoundsException if the argument is not available.
+ */
+ inline fun fifth(): T = invocationOnMock.getArgument(4)
+
+ /**
+ * The last argument.
+ * @throws IndexOutOfBoundsException if the argument is not available.
+ */
+ inline fun last(): T {
+ val lastIndex = invocationOnMock.arguments.size - 1
+ return invocationOnMock.getArgument(lastIndex)
+ }
+
+ /**
+ * The single argument.
+ * @throws IndexOutOfBoundsException if the argument is not available.
+ */
+ inline fun single(): T {
+ val size = invocationOnMock.arguments.size
+ require(size == 1) { "The invocation was expected to have exactly 1 argument but got $size." }
+ return first()
+ }
+
+ /**
+ * The all arguments.
+ */
+ fun all(): List = invocationOnMock.arguments.toList()
}
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..4e20e56c 100644
--- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KStubbing.kt
+++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/KStubbing.kt
@@ -25,21 +25,25 @@
package org.mockito.kotlin
-import org.mockito.kotlin.internal.createInstance
import kotlinx.coroutines.runBlocking
import org.mockito.Mockito
import org.mockito.exceptions.misusing.NotAMockException
+import org.mockito.internal.progress.ThreadSafeMockingProgress.mockingProgress
+import org.mockito.kotlin.internal.createInstance
import org.mockito.stubbing.OngoingStubbing
import org.mockito.stubbing.Stubber
import kotlin.reflect.KClass
-inline fun stubbing(
- mock: T,
- stubbing: KStubbing.(T) -> Unit
-) {
+/**
+ * Apply stubbing on the given mock. The stubbed behavior of the mock can then be specified in a supplied lambda.
+ */
+inline fun stubbing(mock: T, stubbing: KStubbing.(T) -> Unit) {
KStubbing(mock).stubbing(mock)
}
+/**
+ * Apply stubbing on the given mock. The stubbed behavior of the mock can then be specified in a supplied lambda.
+ */
inline fun T.stub(stubbing: KStubbing.(T) -> Unit): T {
return apply { KStubbing(this).stubbing(this) }
}
@@ -49,12 +53,59 @@ 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 methods. Use it when you want the mock to return particular value when particular method is called.
+ *
+ * Simply put: `When the x method is called then return y`
+ */
+ fun on(methodCall: R): OngoingStubbing = mockitoWhen(methodCall)
+
+ /**
+ * Enables stubbing methods. Use it when you want the mock to return particular value when particular method is called.
+ *
+ * Simply put: `When the x method is called then return y`
+ */
+ fun on(methodCall: T.() -> R): OngoingStubbing {
+ return try {
+ mockitoWhen(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 suspend functions. Use it when you want the mock to return particular value when particular suspend function is called.
+ *
+ * Simply put: `When the x suspend function is called then return y`
+ */
+ fun onBlocking(suspendFunctionCall: suspend T.() -> R): CoroutinesOngoingStubbing {
+ return CoroutinesOngoingStubbing(
+ runBlocking { mockitoWhen(mock.suspendFunctionCall()) }
+ )
+ }
+
+ /**
+ * Enables stubbing generic methods with return type [R]. Use it when you want the mock to return particular value when particular method is called.
+ *
+ * Simply put: `When the x method is called then return y of type R`
+ */
+ inline fun onGeneric(noinline methodCall: T.() -> R?): OngoingStubbing {
+ return onGeneric(methodCall, R::class)
+ }
- fun onGeneric(methodCall: T.() -> R?, c: KClass): OngoingStubbing {
+
+ /**
+ * Enables stubbing generic methods with return type [R]. Use it when you want the mock to return particular value when particular method is called.
+ *
+ * Simply put: `When the x method is called then return y of type R`
+ */
+ fun onGeneric(methodCall: T.() -> R?, c: KClass): OngoingStubbing {
val r = try {
mock.methodCall()
- } catch (e: NullPointerException) {
+ } 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.
@@ -62,31 +113,27 @@ class KStubbing(val mock: T) {
// the wanted changes.
createInstance(c)
}
- return Mockito.`when`(r)
+ return mockitoWhen(r)
}
- inline fun onGeneric(noinline methodCall: T.() -> R?): OngoingStubbing {
- return onGeneric(methodCall, R::class)
+ /**
+ * Completes stubbing a method, by addressing the method call to apply a given stubbed [org.mockito.stubbing.Answer] on.
+ * Use it when you want the mock to return particular value when particular method is called.
+ *
+ * Simply put: `Return y when the x method is called`
+ */
+ fun Stubber.on(methodCall: T.() -> Unit) {
+ this.`when`(mock).methodCall()
}
- 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
- )
- }
- }
+ private fun mockitoWhen(methodCall: R): OngoingStubbing {
+ val ongoingStubbing = Mockito.`when`(methodCall)
- fun KStubbing.onBlocking(
- m: suspend T.() -> R
- ): OngoingStubbing {
- return runBlocking { Mockito.`when`(mock.m()) }
- }
+ if (ongoingStubbing.getMock() != mock) {
+ mockingProgress().reset()
+ throw IllegalArgumentException("Stubbing of another mock is not allowed")
+ }
- fun Stubber.on(methodCall: T.() -> Unit) {
- this.`when`(mock).methodCall()
+ return ongoingStubbing
}
}
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..bd647263 100644
--- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/OngoingStubbing.kt
+++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/OngoingStubbing.kt
@@ -25,35 +25,12 @@
package org.mockito.kotlin
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.runBlocking
-import org.mockito.Mockito
import org.mockito.kotlin.internal.KAnswer
import org.mockito.kotlin.internal.SuspendableAnswer
import org.mockito.stubbing.Answer
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.
- *
- * Alias for [Mockito.when].
- */
-@Suppress("NOTHING_TO_INLINE")
-inline fun whenever(methodCall: T): OngoingStubbing {
- return Mockito.`when`(methodCall)!!
-}
-
-/**
- * Enables stubbing suspending methods. Use it when you want the mock to return particular value when particular suspending method is called.
- *
- * Warning: Only one method call can be stubbed in the function.
- * other method calls are ignored!
- */
-fun wheneverBlocking(methodCall: suspend CoroutineScope.() -> T): OngoingStubbing {
- return runBlocking { Mockito.`when`(methodCall()) }
-}
-
/**
* Sets a return value to be returned when the method is called.
*
@@ -72,6 +49,13 @@ fun OngoingStubbing.doReturn(t: T, vararg ts: T): OngoingStubbing {
return thenReturn(t, *ts)
}
+/**
+ * Sets consecutive return values to be returned when the method is called.
+ */
+inline fun OngoingStubbing.doReturnConsecutively(vararg ts: T): OngoingStubbing {
+ return doReturnConsecutively(listOf(*ts))
+}
+
/**
* Sets consecutive return values to be returned when the method is called.
*/
@@ -136,6 +120,15 @@ infix fun OngoingStubbing.doAnswer(answer: (KInvocationOnMock) -> T?): On
return thenAnswer(KAnswer(answer))
}
+/**
+ * Sets a generic Answer for a suspend function using a suspend lambda.
+ *
+ * Deprecated. Use wheneverBlocking() with doAnswer()/thenAnswer() instead.
+ */
+@Deprecated(
+ "Use wheneverBlocking() with doAnswer()/thenAnswer() instead.",
+ level = DeprecationLevel.WARNING
+)
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..315845d5 100644
--- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Stubber.kt
+++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Stubber.kt
@@ -29,7 +29,6 @@ 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
@@ -68,10 +67,19 @@ fun doThrow(vararg toBeThrown: Throwable): Stubber {
return Mockito.doThrow(*toBeThrown)!!
}
-fun Stubber.whenever(mock: T) = `when`(mock)
+fun Stubber.whenever(mock: T): T = `when`(mock)
/**
- * Alias for when with suspending function
+ * Reverse stubber for suspending functions.
+ *
+ * Warning: Only one method call can be stubbed in the function.
+ * Subsequent method calls are ignored!
+ */
+fun Stubber.onBlocking(mock: T, f: suspend T.() -> Unit) =
+ wheneverBlocking(mock, f)
+
+/**
+ * Reverse stubber for suspending functions.
*
* Warning: Only one method call can be stubbed in the function.
* Subsequent method calls are ignored!
diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Whenever.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Whenever.kt
new file mode 100644
index 00000000..657a95f4
--- /dev/null
+++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Whenever.kt
@@ -0,0 +1,70 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2018 Niek Haarman
+ * Copyright (c) 2007 Mockito contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.mockito.kotlin
+
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.runBlocking
+import org.mockito.Mockito
+import org.mockito.kotlin.internal.assertStubbingForNonSuspendableFunctionCall
+import org.mockito.kotlin.internal.assertStubbingForSuspendableFunctionCall
+import org.mockito.stubbing.OngoingStubbing
+
+/**
+ * Enables stubbing methods. Use it when you want the mock to return particular value when particular method is called.
+ *
+ * Alias for [org.mockito.Mockito.when].
+ */
+fun whenever(methodCall: () -> T): OngoingStubbing {
+ return whenever(methodCall())
+}
+
+/**
+ * Enables stubbing methods. Use it when you want the mock to return particular value when particular method is called.
+ *
+ * Alias for [org.mockito.Mockito.when].
+ */
+fun whenever(methodCall: T): OngoingStubbing {
+ val ongoingStubbing: OngoingStubbing = Mockito.`when`(methodCall)
+ assertStubbingForNonSuspendableFunctionCall(ongoingStubbing)
+ return ongoingStubbing
+}
+
+/**
+ * Enables stubbing suspending methods. Use it when you want the mock to return particular value when particular suspending method is called.
+ *
+ * Warning: Only one method call can be stubbed in the function.
+ * other method calls are ignored!
+ */
+fun wheneverBlocking(methodCall: suspend CoroutineScope.() -> T): CoroutinesOngoingStubbing {
+ return CoroutinesOngoingStubbing(
+ runBlocking {
+ val ongoingStubbing: OngoingStubbing = Mockito.`when`(methodCall())!!
+ assertStubbingForSuspendableFunctionCall(ongoingStubbing)
+ ongoingStubbing
+ }
+ )
+}
+
diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/KAnswer.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/KAnswer.kt
index ca62f048..a06728bf 100644
--- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/KAnswer.kt
+++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/KAnswer.kt
@@ -34,9 +34,9 @@ import org.mockito.stubbing.Answer
*/
@Suppress("UNCHECKED_CAST")
internal class KAnswer(
- private val body: (KInvocationOnMock) -> T?
+ private val block: (KInvocationOnMock) -> T?
) : Answer {
override fun answer(invocation: InvocationOnMock): T {
- return body(KInvocationOnMock(invocation)) as T
+ return block.invoke(KInvocationOnMock(invocation)) as T
}
}
diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/OngoingStubbingUtil.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/OngoingStubbingUtil.kt
new file mode 100644
index 00000000..2dc2ae64
--- /dev/null
+++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/internal/OngoingStubbingUtil.kt
@@ -0,0 +1,80 @@
+/*
+ * The MIT License
+ *
+ * Copyright (c) 2018 Niek Haarman
+ * Copyright (c) 2007 Mockito contributors
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ * copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+ * THE SOFTWARE.
+ */
+
+package org.mockito.kotlin.internal
+
+import org.mockito.internal.configuration.plugins.Plugins
+import org.mockito.internal.stubbing.OngoingStubbingImpl
+import org.mockito.stubbing.OngoingStubbing
+import java.lang.reflect.Method
+import kotlin.reflect.jvm.kotlinFunction
+
+
+fun assertStubbingForNonSuspendableFunctionCall(ongoingStubbing: OngoingStubbing) {
+ val method = ongoingStubbing.lastInvocationMethod ?: return
+ val isSuspend = method.kotlinFunction?.isSuspend ?: false
+
+ if (isSuspend) {
+ Plugins
+ .getMockitoLogger()
+ .warn(
+ "For stubbing suspendable function '${method.declaringClass.simpleName}" +
+ ".${method.name}' better use 'wheneverBlocking()' instead, to get full " +
+ "support for stubbing suspend functions."
+ )
+ }
+}
+
+fun assertStubbingForSuspendableFunctionCall(ongoingStubbing: OngoingStubbing) {
+ val method = ongoingStubbing.lastInvocationMethod ?: return
+ val isSuspend = method.kotlinFunction?.isSuspend ?: false
+
+ if (!isSuspend) {
+ warn(
+ "For stubbing non-suspendable function '${method.declaringClass.simpleName}" +
+ ".${method.name}' better use 'whenever()' instead."
+ )
+ }
+}
+
+private fun warn(message: String) {
+ Plugins.getMockitoLogger().warn(message)
+}
+
+val OngoingStubbing.lastInvocationMethodIsSuspend: Boolean?
+ get() {
+ val method = this.lastInvocationMethod
+ if (method == null) {
+ warn("Failed to determine last invocation on a mock.")
+ }
+ return method?.kotlinFunction?.isSuspend
+ }
+
+private val OngoingStubbing.lastInvocationMethod: Method?
+ get(): Method? =
+ (this as? OngoingStubbingImpl)
+ ?.registeredInvocations
+ ?.lastOrNull()
+ ?.method
diff --git a/mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt b/mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt
deleted file mode 100644
index fa5b9178..00000000
--- a/mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt
+++ /dev/null
@@ -1,537 +0,0 @@
-@file:Suppress("EXPERIMENTAL_FEATURE_WARNING")
-
-package test
-
-import com.nhaarman.expect.expect
-import kotlinx.coroutines.*
-import kotlinx.coroutines.channels.actor
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertThrows
-import org.junit.Test
-import org.mockito.InOrder
-import org.mockito.kotlin.*
-import java.util.*
-
-class CoroutinesTest {
-
- @Test
- fun stubbingSuspending() {
- /* Given */
- val m = mock {
- onBlocking { suspending() } doReturn 42
- }
-
- /* When */
- val result = runBlocking { m.suspending() }
-
- /* Then */
- expect(result).toBe(42)
- }
-
- @Test
- fun stubbingSuspending_usingSuspendingFunction() {
- /* Given */
- val m = mock {
- onBlocking { suspending() } doReturn runBlocking { SomeClass().result(42) }
- }
-
- /* When */
- val result = runBlocking { m.suspending() }
-
- /* Then */
- expect(result).toBe(42)
- }
-
- @Test
- fun stubbingSuspending_runBlocking() = runBlocking {
- /* Given */
- val m = mock {
- onBlocking { suspending() } doReturn 42
- }
-
- /* When */
- val result = m.suspending()
-
- /* Then */
- expect(result).toBe(42)
- }
-
- @Test
- fun stubbingSuspending_wheneverBlocking() {
- /* Given */
- val m: SomeInterface = mock()
- wheneverBlocking { m.suspending() }
- .doReturn(42)
-
- /* When */
- val result = runBlocking { m.suspending() }
-
- /* Then */
- expect(result).toBe(42)
- }
-
- @Test
- fun stubbingSuspending_doReturn() {
- /* Given */
- val m = spy(SomeClass())
- doReturn(10)
- .wheneverBlocking(m) {
- delaying()
- }
-
- /* When */
- val result = runBlocking { m.delaying() }
-
- /* Then */
- expect(result).toBe(10)
- }
-
- @Test
- fun stubbingNonSuspending() {
- /* Given */
- val m = mock {
- onBlocking { nonsuspending() } doReturn 42
- }
-
- /* When */
- val result = m.nonsuspending()
-
- /* Then */
- expect(result).toBe(42)
- }
-
- @Test
- fun stubbingNonSuspending_runBlocking() = runBlocking {
- /* Given */
- val m = mock {
- onBlocking { nonsuspending() } doReturn 42
- }
-
- /* When */
- val result = m.nonsuspending()
-
- /* Then */
- expect(result).toBe(42)
- }
-
- @Test
- fun delayingResult() {
- /* Given */
- val m = SomeClass()
-
- /* When */
- val result = runBlocking { m.delaying() }
-
- /* Then */
- expect(result).toBe(42)
- }
-
- @Test
- fun delayingResult_runBlocking() = runBlocking {
- /* Given */
- val m = SomeClass()
-
- /* When */
- val result = m.delaying()
-
- /* Then */
- expect(result).toBe(42)
- }
-
- @Test
- fun verifySuspendFunctionCalled() {
- /* Given */
- val m = mock()
-
- /* When */
- runBlocking { m.suspending() }
-
- /* Then */
- runBlocking { verify(m).suspending() }
- }
-
- @Test
- fun verifySuspendFunctionCalled_runBlocking() = runBlocking {
- val m = mock()
-
- m.suspending()
-
- verify(m).suspending()
- }
-
- @Test
- fun verifySuspendFunctionCalled_verifyBlocking() {
- val m = mock()
-
- runBlocking { m.suspending() }
-
- verifyBlocking(m) { suspending() }
- }
-
- @Test
- fun verifyAtLeastOnceSuspendFunctionCalled_verifyBlocking() {
- val m = mock()
-
- runBlocking { m.suspending() }
- runBlocking { m.suspending() }
-
- verifyBlocking(m, atLeastOnce()) { suspending() }
- }
-
- @Test
- fun verifySuspendMethod() = runBlocking {
- val testSubject: SomeInterface = mock()
-
- testSubject.suspending()
-
- inOrder(testSubject) {
- verify(testSubject).suspending()
- }
- }
-
- @Test
- fun answerWithSuspendFunction() = runBlocking {
- val fixture: SomeInterface = mock()
-
- whenever(fixture.suspendingWithArg(any())).doSuspendableAnswer {
- withContext(Dispatchers.Default) { it.getArgument(0) }
- }
-
- assertEquals(5, fixture.suspendingWithArg(5))
- }
-
- @Test
- fun inplaceAnswerWithSuspendFunction() = runBlocking {
- val fixture: SomeInterface = mock {
- onBlocking { suspendingWithArg(any()) } doSuspendableAnswer {
- withContext(Dispatchers.Default) { it.getArgument(0) }
- }
- }
-
- assertEquals(5, fixture.suspendingWithArg(5))
- }
-
- @Test
- fun callFromSuspendFunction() = runBlocking {
- val fixture: SomeInterface = mock()
-
- whenever(fixture.suspendingWithArg(any())).doSuspendableAnswer {
- withContext(Dispatchers.Default) { it.getArgument(0) }
- }
-
- val result = async {
- val answer = fixture.suspendingWithArg(5)
-
- Result.success(answer)
- }
-
- assertEquals(5, result.await().getOrThrow())
- }
-
- @Test
- @OptIn(ObsoleteCoroutinesApi::class)
- fun callFromActor() = runBlocking {
- val fixture: SomeInterface = mock()
-
- whenever(fixture.suspendingWithArg(any())).doSuspendableAnswer {
- withContext(Dispatchers.Default) { it.getArgument(0) }
- }
-
- val actor = actor> {
- for (element in channel) {
- fixture.suspendingWithArg(element.get())
- }
- }
-
- actor.send(Optional.of(10))
- actor.close()
-
- verify(fixture).suspendingWithArg(10)
-
- Unit
- }
-
- @Test
- fun answerWithSuspendFunctionWithoutArgs() = runBlocking {
- val fixture: SomeInterface = mock()
-
- whenever(fixture.suspending()).doSuspendableAnswer {
- withContext(Dispatchers.Default) { 42 }
- }
-
- assertEquals(42, fixture.suspending())
- }
-
- @Test
- fun answerWithSuspendFunctionWithDestructuredArgs() = runBlocking {
- val fixture: SomeInterface = mock()
-
- whenever(fixture.suspendingWithArg(any())).doSuspendableAnswer { (i: Int) ->
- withContext(Dispatchers.Default) { i }
- }
-
- assertEquals(5, fixture.suspendingWithArg(5))
- }
-
- @Test
- fun willAnswerWithControlledSuspend() = runBlocking {
- val fixture: SomeInterface = mock()
-
- val job = Job()
-
- whenever(fixture.suspending()).doSuspendableAnswer {
- job.join()
- 5
- }
-
- val asyncTask = async {
- fixture.suspending()
- }
-
- job.complete()
-
- withTimeout(100) {
- assertEquals(5, asyncTask.await())
- }
- }
-
- @Test
- fun stubberAnswerWithSuspendFunction() = runBlocking {
- val fixture: SomeInterface = mock()
-
- doSuspendableAnswer {
- withContext(Dispatchers.Default) { it.getArgument(0) }
- }.whenever(fixture).suspendingWithArg(any())
-
- assertEquals(5, fixture.suspendingWithArg(5))
- }
-
- @Test
- fun stubberCallFromSuspendFunction() = runBlocking {
- val fixture: SomeInterface = mock()
-
- doSuspendableAnswer {
- withContext(Dispatchers.Default) { it.getArgument(0) }
- }.whenever(fixture).suspendingWithArg(any())
-
- val result = async {
- val answer = fixture.suspendingWithArg(5)
-
- Result.success(answer)
- }
-
- assertEquals(5, result.await().getOrThrow())
- }
-
- @Test
- @OptIn(ObsoleteCoroutinesApi::class)
- fun stubberCallFromActor() = runBlocking {
- val fixture: SomeInterface = mock()
-
- doSuspendableAnswer {
- withContext(Dispatchers.Default) { it.getArgument(0) }
- }.whenever(fixture).suspendingWithArg(any())
-
- val actor = actor> {
- for (element in channel) {
- fixture.suspendingWithArg(element.get())
- }
- }
-
- actor.send(Optional.of(10))
- actor.close()
-
- verify(fixture).suspendingWithArg(10)
-
- Unit
- }
-
- @Test
- fun stubberAnswerWithSuspendFunctionWithoutArgs() = runBlocking {
- val fixture: SomeInterface = mock()
-
- doSuspendableAnswer {
- withContext(Dispatchers.Default) { 42 }
- }.whenever(fixture).suspending()
-
- assertEquals(42, fixture.suspending())
- }
-
- @Test
- fun stubberAnswerWithSuspendFunctionWithDestructuredArgs() = runBlocking {
- val fixture: SomeInterface = mock()
-
- doSuspendableAnswer { (i: Int) ->
- withContext(Dispatchers.Default) { i }
- }.whenever(fixture).suspendingWithArg(any())
-
- assertEquals(5, fixture.suspendingWithArg(5))
- }
-
- @Test
- fun stubberWillAnswerWithControlledSuspend() = runBlocking {
- val fixture: SomeInterface = mock()
-
- val job = Job()
-
- doSuspendableAnswer {
- job.join()
- 5
- }.whenever(fixture).suspending()
-
- val asyncTask = async {
- fixture.suspending()
- }
-
- job.complete()
-
- withTimeout(100) {
- assertEquals(5, asyncTask.await())
- }
- }
-
- @Test
- fun inOrderRemainsCompatible() {
- /* Given */
- val fixture: SomeInterface = mock()
-
- /* When */
- val inOrder = inOrder(fixture)
-
- /* Then */
- expect(inOrder).toBeInstanceOf()
- }
-
- @Test
- fun inOrderSuspendingCalls() {
- /* Given */
- val fixtureOne: SomeInterface = mock()
- val fixtureTwo: SomeInterface = mock()
-
- /* When */
- runBlocking {
- fixtureOne.suspending()
- fixtureTwo.suspending()
- }
-
- /* Then */
- val inOrder = inOrder(fixtureOne, fixtureTwo)
- inOrder.verifyBlocking(fixtureOne) { suspending() }
- inOrder.verifyBlocking(fixtureTwo) { suspending() }
- }
-
- @Test
- fun inOrderSuspendingCallsFailure() {
- /* Given */
- val fixtureOne: SomeInterface = mock()
- val fixtureTwo: SomeInterface = mock()
-
- /* When */
- runBlocking {
- fixtureOne.suspending()
- fixtureTwo.suspending()
- }
-
- /* Then */
- val inOrder = inOrder(fixtureOne, fixtureTwo)
- inOrder.verifyBlocking(fixtureTwo) { suspending() }
- assertThrows(AssertionError::class.java) {
- inOrder.verifyBlocking(fixtureOne) { suspending() }
- }
- }
-
- @Test
- fun inOrderBlockSuspendingCalls() {
- /* Given */
- val fixtureOne: SomeInterface = mock()
- val fixtureTwo: SomeInterface = mock()
-
- /* When */
- runBlocking {
- fixtureOne.suspending()
- fixtureTwo.suspending()
- }
-
- /* Then */
- inOrder(fixtureOne, fixtureTwo) {
- verifyBlocking(fixtureOne) { suspending() }
- verifyBlocking(fixtureTwo) { suspending() }
- }
- }
-
- @Test
- fun inOrderBlockSuspendingCallsFailure() {
- /* Given */
- val fixtureOne: SomeInterface = mock()
- val fixtureTwo: SomeInterface = mock()
-
- /* When */
- runBlocking {
- fixtureOne.suspending()
- fixtureTwo.suspending()
- }
-
- /* Then */
- inOrder(fixtureOne, fixtureTwo) {
- verifyBlocking(fixtureTwo) { suspending() }
- assertThrows(AssertionError::class.java) {
- verifyBlocking(fixtureOne) { suspending() }
- }
- }
- }
-
- @Test
- fun inOrderOnObjectSuspendingCalls() {
- /* Given */
- val fixture: SomeInterface = mock()
-
- /* When */
- runBlocking {
- fixture.suspendingWithArg(1)
- fixture.suspendingWithArg(2)
- }
-
- /* Then */
- fixture.inOrder {
- verifyBlocking { suspendingWithArg(1) }
- verifyBlocking { suspendingWithArg(2) }
- }
- }
-
- @Test
- fun inOrderOnObjectSuspendingCallsFailure() {
- /* Given */
- val fixture: SomeInterface = mock()
-
- /* When */
- runBlocking {
- fixture.suspendingWithArg(1)
- fixture.suspendingWithArg(2)
- }
-
- /* Then */
- fixture.inOrder {
- verifyBlocking { suspendingWithArg(2) }
- assertThrows(AssertionError::class.java) {
- verifyBlocking { suspendingWithArg(1) }
- }
- }
- }
-}
-
-interface SomeInterface {
-
- suspend fun suspending(): Int
- suspend fun suspendingWithArg(arg: Int): Int
- fun nonsuspending(): Int
-}
-
-open class SomeClass {
-
- suspend fun result(r: Int) = withContext(Dispatchers.Default) { r }
-
- open suspend fun delaying() = withContext(Dispatchers.Default) {
- delay(100)
- 42
- }
-}
diff --git a/tests/build.gradle b/tests/build.gradle
index dd447d93..6bb524c8 100644
--- a/tests/build.gradle
+++ b/tests/build.gradle
@@ -19,7 +19,7 @@ dependencies {
testImplementation 'junit:junit:4.13.2'
testImplementation "com.nhaarman:expect.kt:1.0.1"
- testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.9.0-RC"
+ testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2"
}
tasks.withType(KotlinCompile).configureEach {
diff --git a/tests/src/test/kotlin/org/mockito/kotlin/internal/KAnswerTest.kt b/tests/src/test/kotlin/org/mockito/kotlin/internal/KAnswerTest.kt
new file mode 100644
index 00000000..5fc20e3d
--- /dev/null
+++ b/tests/src/test/kotlin/org/mockito/kotlin/internal/KAnswerTest.kt
@@ -0,0 +1,141 @@
+package org.mockito.kotlin.internal
+
+import com.nhaarman.expect.expect
+import org.junit.Test
+import org.mockito.kotlin.anyVararg
+import org.mockito.kotlin.doAnswer
+import org.mockito.kotlin.mock
+import test.SynchronousFunctions
+import test.assertThrows
+
+class KAnswerTest {
+ @Test
+ fun `should answer with first invocation argument`() {
+ /* Given */
+ val mock = mock {
+ on { varargStringResult(anyVararg()) } doAnswer { it.first() }
+ }
+
+ /* When */
+ val result = mock.varargStringResult("A", "B", "C", "D", "E")
+
+ /* Then */
+ expect(result).toBe("A")
+ }
+
+ @Test
+ fun `should answer with second invocation argument`() {
+ /* Given */
+ val mock = mock {
+ on { varargStringResult(anyVararg()) } doAnswer { it.second() }
+ }
+
+ /* When */
+ val result = mock.varargStringResult("A", "B", "C", "D", "E")
+
+ /* Then */
+ expect(result).toBe("B")
+ }
+
+ @Test
+ fun `should answer with third invocation argument`() {
+ /* Given */
+ val mock = mock {
+ on { varargStringResult(anyVararg()) } doAnswer { it.third() }
+ }
+
+ /* When */
+ val result = mock.varargStringResult("A", "B", "C", "D", "E")
+
+ /* Then */
+ expect(result).toBe("C")
+ }
+
+ @Test
+ fun `should answer with fourth invocation argument`() {
+ /* Given */
+ val mock = mock {
+ on { varargStringResult(anyVararg()) } doAnswer { it.fourth() }
+ }
+
+ /* When */
+ val result = mock.varargStringResult("A", "B", "C", "D", "E")
+
+ /* Then */
+ expect(result).toBe("D")
+ }
+
+ @Test
+ fun `should answer with fifth invocation argument`() {
+ /* Given */
+ val mock = mock {
+ on { varargStringResult(anyVararg()) } doAnswer { it.fifth() }
+ }
+
+ /* When */
+ val result = mock.varargStringResult("A", "B", "C", "D", "E")
+
+ /* Then */
+ expect(result).toBe("E")
+ }
+
+ @Test
+ fun `should answer with last invocation argument`() {
+ /* Given */
+ val mock = mock {
+ on { varargStringResult(anyVararg()) } doAnswer { it.last() }
+ }
+
+ /* When */
+ val result = mock.varargStringResult("A", "B", "C", "D", "E")
+
+ /* Then */
+ expect(result).toBe("E")
+ }
+
+ @Test
+ fun `should answer with all invocation arguments`() {
+ /* Given */
+ val mock = mock {
+ on { varargStringResult(anyVararg()) } doAnswer {
+ it.all().joinToString("; ")
+ }
+ }
+
+ /* When */
+ val result = mock.varargStringResult("A", "B", "C", "D", "E")
+
+ /* Then */
+ expect(result).toBe("A; B; C; D; E")
+ }
+
+ @Test
+ fun `should answer with single invocation argument`() {
+ /* Given */
+ val mock = mock {
+ on { varargStringResult(anyVararg()) } doAnswer { it.single() }
+ }
+
+ /* When */
+ val result = mock.varargStringResult("A")
+
+ /* Then */
+ expect(result).toBe("A")
+ }
+
+ @Test
+ fun `should throw when trying to answer with single invocation argument, but with actually more`() {
+ /* Given */
+ val mock = mock {
+ on { varargStringResult(anyVararg()) } doAnswer { it.single() }
+ }
+
+ /* When, Then */
+ val exception: IllegalArgumentException = assertThrows {
+ mock.varargStringResult("A", "B")
+ }
+
+ /* Then */
+ expect(exception.message).toContain("xpected to have exactly 1 argument but got 2")
+ }
+}
diff --git a/tests/src/test/kotlin/test/AdditionalMatchersTest.kt b/tests/src/test/kotlin/test/AdditionalMatchersTest.kt
index bb6f5ede..9e42ed64 100644
--- a/tests/src/test/kotlin/test/AdditionalMatchersTest.kt
+++ b/tests/src/test/kotlin/test/AdditionalMatchersTest.kt
@@ -7,7 +7,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testGeq() {
- mock().apply {
+ mock().apply {
int(1)
verify(this).int(geq(0))
verify(this).int(geq(1))
@@ -17,7 +17,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testLeq() {
- mock().apply {
+ mock().apply {
int(1)
verify(this).int(leq(2))
verify(this).int(leq(1))
@@ -27,7 +27,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testGt() {
- mock().apply {
+ mock().apply {
int(1)
verify(this).int(gt(0))
verify(this, never()).int(gt(1))
@@ -36,7 +36,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testLt() {
- mock().apply {
+ mock().apply {
int(1)
verify(this).int(lt(2))
verify(this, never()).int(lt(1))
@@ -45,7 +45,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testCmpEq() {
- mock().apply {
+ mock().apply {
int(1)
verify(this).int(cmpEq(1))
verify(this, never()).int(cmpEq(2))
@@ -54,7 +54,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testAryEqBoolean() {
- mock().apply {
+ mock().apply {
booleanArray(booleanArrayOf(true, false, true))
verify(this).booleanArray(aryEq(booleanArrayOf(true, false, true)))
verify(this, never()).booleanArray(aryEq(booleanArrayOf(true, false)))
@@ -63,7 +63,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testAryEqByte() {
- mock().apply {
+ mock().apply {
byteArray(byteArrayOf(1, 2, 3))
verify(this).byteArray(aryEq(byteArrayOf(1, 2, 3)))
verify(this, never()).byteArray(aryEq(byteArrayOf(1, 2)))
@@ -72,7 +72,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testAryEqShort() {
- mock().apply {
+ mock().apply {
shortArray(shortArrayOf(1, 2, 3))
verify(this).shortArray(aryEq(shortArrayOf(1, 2, 3)))
verify(this, never()).shortArray(aryEq(shortArrayOf(1, 2)))
@@ -81,7 +81,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testAryEqInt() {
- mock().apply {
+ mock().apply {
intArray(intArrayOf(1, 2, 3))
verify(this).intArray(aryEq(intArrayOf(1, 2, 3)))
verify(this, never()).intArray(aryEq(intArrayOf(1, 2)))
@@ -90,7 +90,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testAryEqLong() {
- mock().apply {
+ mock().apply {
longArray(longArrayOf(1, 2, 3))
verify(this).longArray(aryEq(longArrayOf(1, 2, 3)))
verify(this, never()).longArray(aryEq(longArrayOf(1, 2)))
@@ -99,7 +99,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testAryEqChar() {
- mock().apply {
+ mock().apply {
charArray(charArrayOf('1', '2', '3'))
verify(this).charArray(aryEq(charArrayOf('1', '2', '3')))
verify(this, never()).charArray(aryEq(charArrayOf('1', '2')))
@@ -108,7 +108,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testAryEqFloat() {
- mock().apply {
+ mock().apply {
floatArray(floatArrayOf(1f, 2f, 3.4f))
verify(this).floatArray(aryEq(floatArrayOf(1f, 2f, 3.4f)))
verify(this, never()).floatArray(aryEq(floatArrayOf(1f, 2f)))
@@ -117,7 +117,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testAryEqDouble() {
- mock().apply {
+ mock().apply {
doubleArray(doubleArrayOf(1.0, 2.0, 3.4))
verify(this).doubleArray(aryEq(doubleArrayOf(1.0, 2.0, 3.4)))
verify(this, never()).doubleArray(aryEq(doubleArrayOf(1.0, 2.0)))
@@ -126,7 +126,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testAryEq() {
- mock().apply {
+ mock().apply {
stringArray(arrayOf("Hello", "there"))
verify(this).stringArray(aryEq(arrayOf("Hello", "there")))
verify(this, never()).stringArray(aryEq(arrayOf("Hello")))
@@ -135,7 +135,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testFind() {
- mock().apply {
+ mock().apply {
string("Hello")
verify(this).string(find("l+o$".toRegex()))
verify(this, never()).string(find("l$".toRegex()))
@@ -144,7 +144,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testAnd() {
- mock().apply {
+ mock().apply {
int(5)
verify(this).int(and(geq(4), leq(6)))
verify(this, never()).int(and(geq(4), leq(4)))
@@ -153,7 +153,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testOr() {
- mock().apply {
+ mock().apply {
int(5)
verify(this).int(and(gt(4), lt(6)))
verify(this, never()).int(and(gt(4), lt(4)))
@@ -162,7 +162,7 @@ class AdditionalCaptorsTest : TestBase() {
@Test
fun testNot() {
- mock().apply {
+ mock().apply {
int(5)
verify(this).int(not(eq(4)))
verify(this, never()).int(not(eq(5)))
diff --git a/tests/src/test/kotlin/test/ArgumentCaptorTest.kt b/tests/src/test/kotlin/test/ArgumentCaptorTest.kt
index 14382a5c..891f7ff5 100644
--- a/tests/src/test/kotlin/test/ArgumentCaptorTest.kt
+++ b/tests/src/test/kotlin/test/ArgumentCaptorTest.kt
@@ -101,7 +101,7 @@ class ArgumentCaptorTest : TestBase() {
@Test
fun argumentCaptor_withNullValue_usingNonNullable() {
/* Given */
- val m: Methods = mock()
+ val m: SynchronousFunctions = mock()
/* When */
m.nullableString(null)
@@ -115,7 +115,7 @@ class ArgumentCaptorTest : TestBase() {
@Test
fun argumentCaptor_withNullValue_usingNullable() {
/* Given */
- val m: Methods = mock()
+ val m: SynchronousFunctions = mock()
/* When */
m.nullableString(null)
@@ -173,7 +173,7 @@ class ArgumentCaptorTest : TestBase() {
@Test
fun argumentCaptor_multipleValuesIncludingNull() {
/* Given */
- val m: Methods = mock()
+ val m: SynchronousFunctions = mock()
/* When */
m.nullableString("test")
@@ -188,7 +188,7 @@ class ArgumentCaptorTest : TestBase() {
@Test
fun argumentCaptor_callProperties() {
/* Given */
- val m: Methods = mock()
+ val m: SynchronousFunctions = mock()
/* When */
m.int(1)
@@ -211,7 +211,7 @@ class ArgumentCaptorTest : TestBase() {
@Test(expected = IndexOutOfBoundsException::class)
fun argumentCaptor_callPropertyNotAvailable() {
/* Given */
- val m: Methods = mock()
+ val m: SynchronousFunctions = mock()
/* When */
m.int(1)
@@ -259,7 +259,7 @@ class ArgumentCaptorTest : TestBase() {
@Test
fun argumentCaptor_vararg() {
/* Given */
- val m: Methods = mock()
+ val m: SynchronousFunctions = mock()
/* When */
m.varargBooleanResult("a", "b", "c")
@@ -273,7 +273,7 @@ class ArgumentCaptorTest : TestBase() {
@Test
fun argumentCaptor_empty_vararg() {
/* Given */
- val m: Methods = mock()
+ val m: SynchronousFunctions = mock()
/* When */
m.varargBooleanResult()
@@ -287,7 +287,7 @@ class ArgumentCaptorTest : TestBase() {
@Test
fun argumentCaptor_arg_vararg() {
/* Given */
- val m: Methods = mock()
+ val m: SynchronousFunctions = mock()
/* When */
m.argAndVararg("first", "a", "b", "c")
@@ -301,7 +301,7 @@ class ArgumentCaptorTest : TestBase() {
@Test
fun argumentCaptor_intarray() {
/* Given */
- val m: Methods = mock()
+ val m: SynchronousFunctions = mock()
/* When */
m.intArray(intArrayOf(1, 2, 3))
@@ -315,7 +315,7 @@ class ArgumentCaptorTest : TestBase() {
@Test
fun argumentCaptor_array() {
/* Given */
- val m: Methods = mock()
+ val m: SynchronousFunctions = mock()
/* When */
m.stringArray(arrayOf("a", "b", "c"))
@@ -329,7 +329,7 @@ class ArgumentCaptorTest : TestBase() {
@Test
fun argumentCaptor_empty_array() {
/* Given */
- val m: Methods = mock()
+ val m: SynchronousFunctions = mock()
/* When */
m.stringArray(arrayOf())
@@ -343,7 +343,7 @@ class ArgumentCaptorTest : TestBase() {
@Test
fun argumentCaptor_value_class() {
/* Given */
- val m: Methods = mock()
+ val m: SynchronousFunctions = mock()
val valueClass = ValueClass("Content")
/* When */
@@ -358,7 +358,7 @@ class ArgumentCaptorTest : TestBase() {
@Test
fun argumentCaptor_value_class_withNullValue_usingNonNullable() {
/* Given */
- val m: Methods = mock()
+ val m: SynchronousFunctions = mock()
/* When */
m.nullableValueClass(null)
@@ -372,7 +372,7 @@ class ArgumentCaptorTest : TestBase() {
@Test
fun argumentCaptor_value_class_withNullValue_usingNullable() {
/* Given */
- val m: Methods = mock()
+ val m: SynchronousFunctions = mock()
/* When */
m.nullableValueClass(null)
diff --git a/tests/src/test/kotlin/test/BDDMockitoTest.kt b/tests/src/test/kotlin/test/BDDMockitoTest.kt
index 0472163f..457bf406 100644
--- a/tests/src/test/kotlin/test/BDDMockitoTest.kt
+++ b/tests/src/test/kotlin/test/BDDMockitoTest.kt
@@ -10,7 +10,7 @@ class BDDMockitoTest {
@Test
fun given_will_properlyStubs() {
/* Given */
- val mock = mock()
+ val mock = mock()
/* When */
given(mock.stringResult()) will Answer { "Test" }
@@ -22,7 +22,7 @@ class BDDMockitoTest {
@Test
fun given_willReturn_properlyStubs() {
/* Given */
- val mock = mock()
+ val mock = mock()
/* When */
given(mock.stringResult()).willReturn("Test")
@@ -34,7 +34,7 @@ class BDDMockitoTest {
@Test
fun givenLambda_willReturn_properlyStubs() {
/* Given */
- val mock = mock()
+ val mock = mock()
/* When */
given { mock.stringResult() }.willReturn("Test")
@@ -46,7 +46,7 @@ class BDDMockitoTest {
@Test
fun given_willReturnLambda_properlyStubs() {
/* Given */
- val mock = mock()
+ val mock = mock()
/* When */
given(mock.stringResult()).willReturn { "Test" }
@@ -58,7 +58,7 @@ class BDDMockitoTest {
@Test
fun givenLambda_willReturnLambda_properlyStubs() {
/* Given */
- val mock = mock()
+ val mock = mock()
/* When */
given { mock.stringResult() } willReturn { "Test" }
@@ -70,7 +70,7 @@ class BDDMockitoTest {
@Test
fun given_willAnswer_properlyStubs() {
/* Given */
- val mock = mock()
+ val mock = mock()
/* When */
given(mock.stringResult()).willAnswer { "Test" }
@@ -82,7 +82,7 @@ class BDDMockitoTest {
@Test
fun given_willAnswerInfix_properlyStubs() {
/* Given */
- val mock = mock()
+ val mock = mock()
/* When */
given(mock.stringResult()) willAnswer { "Test" }
@@ -94,7 +94,7 @@ class BDDMockitoTest {
@Test
fun given_willAnswerInfix_withInvocationInfo_properlyStubs() {
/* Given */
- val mock = mock()
+ val mock = mock()
/* When */
given(mock.stringResult(any())) willAnswer { invocation ->
@@ -109,7 +109,7 @@ class BDDMockitoTest {
@Test(expected = IllegalStateException::class)
fun given_willThrowInfix_properlyStubs() {
/* Given */
- val mock = mock()
+ val mock = mock()
/* When */
given(mock.stringResult()) willThrow { IllegalStateException() }
@@ -119,13 +119,13 @@ class BDDMockitoTest {
@Test
fun then() {
/* Given */
- val mock = mock()
+ val mock = mock()
whenever(mock.stringResult()).thenReturn("Test")
/* When */
mock.stringResult()
/* Then */
- org.mockito.kotlin.then(mock).should().stringResult()
+ then(mock).should().stringResult()
}
}
diff --git a/tests/src/test/kotlin/test/Classes.kt b/tests/src/test/kotlin/test/Classes.kt
index d88c766d..4830233d 100644
--- a/tests/src/test/kotlin/test/Classes.kt
+++ b/tests/src/test/kotlin/test/Classes.kt
@@ -1,5 +1,7 @@
package test
+import kotlinx.coroutines.delay
+
/*
* The MIT License
*
@@ -42,11 +44,9 @@ open class Open {
class Closed
-interface Methods {
-
+interface SynchronousFunctions {
fun closed(c: Closed)
fun classClosed(c: Class)
- suspend fun coroutinesClosed(c: Closed)
fun closedArray(a: Array)
fun closedNullableArray(a: Array)
fun closedCollection(c: Collection)
@@ -77,10 +77,9 @@ interface Methods {
fun stringResult(): String
fun stringResult(s: String): String
fun nullableStringResult(): String?
- fun builderMethod(): Methods
+ fun builderMethod(): SynchronousFunctions
fun varargBooleanResult(vararg values: String): Boolean
- suspend fun coroutinesClosedBooleanResult(c: Closed): Boolean
- suspend fun coroutinesClassClosedBooleanResult(c: Class): Boolean
+ fun varargStringResult(vararg values: String): String
fun stringArray(a: Array)
fun argAndVararg(s: String, vararg a: String)
@@ -89,6 +88,23 @@ interface Methods {
fun valueClass(v: ValueClass)
fun nullableValueClass(v: ValueClass?)
fun nestedValueClass(v: NestedValueClass)
+ fun valueClassResult(): ValueClass
+ fun nullableValueClassResult(): ValueClass?
+ fun nestedValueClassResult(): NestedValueClass
+}
+
+interface SuspendFunctions {
+ suspend fun closed(c: Closed)
+ suspend fun closedBooleanResult(c: Closed): Boolean
+ suspend fun classClosedBooleanResult(c: Class): Boolean
+ suspend fun stringResult(): String
+ suspend fun stringResult(s: String): String
+ suspend fun stringResult(s1: String, s2: String): String
+ suspend fun nullableStringResult(): String?
+ suspend fun valueClassResult(): ValueClass
+ suspend fun nullableValueClassResult(): ValueClass?
+ suspend fun nestedValueClassResult(): NestedValueClass
+ suspend fun builderMethod(): SuspendFunctions
}
@JvmInline
diff --git a/tests/src/test/kotlin/test/CoroutinesOngoingStubbingTest.kt b/tests/src/test/kotlin/test/CoroutinesOngoingStubbingTest.kt
new file mode 100644
index 00000000..91be842e
--- /dev/null
+++ b/tests/src/test/kotlin/test/CoroutinesOngoingStubbingTest.kt
@@ -0,0 +1,344 @@
+package test
+
+import com.nhaarman.expect.expect
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.runTest
+import org.junit.Ignore
+import org.junit.Test
+import org.mockito.Mockito
+import org.mockito.kotlin.any
+import org.mockito.kotlin.mock
+import org.mockito.stubbing.Answer
+
+class CoroutinesOngoingStubbingTest {
+ @Test
+ fun `should mock suspendable methodCall`() {
+ /* Given */
+ val mock = mock {
+ onBlocking { stringResult() } doReturn "A"
+ }
+
+ /* When */
+ val result = runBlocking { mock.stringResult() }
+
+ /* Then */
+ expect(result).toBe("A")
+ }
+
+ @Test
+ fun `should mock suspendable methodCall within a coroutine scope`() = runTest {
+ /* Given */
+ val mock = mock {
+ onBlocking { stringResult() } doReturn "A"
+ }
+
+ /* When */
+ val result = mock.stringResult()
+
+ /* Then */
+ expect(result).toBe("A")
+ }
+
+ @Test
+ fun `should mock consecutive suspendable methodCalls`() {
+ /* Given */
+ val mock = mock {
+ onBlocking { stringResult() }.doReturn("A", "B", "C")
+ }
+
+ /* When */
+ val result = runBlocking {
+ (1..3).map { _ ->
+ mock.stringResult()
+ }
+ }
+
+ /* Then */
+ expect(result).toBe(listOf("A", "B", "C"))
+ }
+
+ @Test
+ fun `should mock consecutive suspendable methodCalls by a list of answers`() {
+ /* Given */
+ val mock = mock {
+ onBlocking { stringResult() } doReturnConsecutively listOf("A", "B", "C")
+ }
+
+ /* When */
+ val result = runBlocking {
+ (1..3).map { _ ->
+ mock.stringResult()
+ }
+ }
+
+ /* Then */
+ expect(result).toBe(listOf("A", "B", "C"))
+ }
+
+ @Ignore("Default answers do not yet work for coroutines, see https://github.com/mockito/mockito-kotlin/issues/550")
+ @Test
+ fun `should mock builder method returning mock itself via defaultAnswer`() {
+ /* Given */
+ val mock = mock(defaultAnswer = Mockito.RETURNS_SELF)
+
+ /* When */
+ val result = runBlocking { mock.builderMethod() }
+
+ /* Then */
+ expect(result).toBeTheSameAs(mock)
+ }
+
+ @Ignore("Default answers do not yet work for coroutines, see https://github.com/mockito/mockito-kotlin/issues/550")
+ @Test
+ fun `should mock builder method returning mock itself via answer`() {
+ /* Given */
+ val mock = mock {
+ onBlocking { builderMethod() } doAnswer Mockito.RETURNS_SELF
+ }
+
+ /* When */
+ val result = runBlocking { mock.builderMethod() }
+
+ /* Then */
+ expect(result).toBeTheSameAs(mock)
+ }
+
+ @Test
+ fun `should mock builder method returning mock itself`() {
+ /* Given */
+ val mock = mock { mock ->
+ onBlocking { builderMethod() } doReturn mock
+ }
+
+ /* When */
+ val result = runBlocking { mock.builderMethod() }
+
+ /* Then */
+ expect(result).toBe(mock)
+ }
+
+ @Test
+ fun `should mock suspendable methodCall with nullable result`() {
+ /* Given */
+ val mock = mock {
+ onBlocking { nullableStringResult() } doReturn "Test"
+ }
+
+ /* When */
+ val result = runBlocking { mock.nullableStringResult() }
+
+ /* Then */
+ expect(result).toBe("Test")
+ }
+
+ @Test
+ fun `should throw exception instance on suspendable methodCall`() {
+ /* Given */
+ val mock = mock {
+ onBlocking { builderMethod() } doThrow IllegalArgumentException()
+ }
+
+ /* When, Then */
+ runBlocking {
+ assertThrows {
+ mock.builderMethod()
+ }
+ }
+ }
+
+ @Test
+ fun `should throw exception class on suspendable methodCall`() {
+ /* Given */
+ val mock = mock {
+ onBlocking { builderMethod() } doThrow IllegalArgumentException::class
+ }
+
+ /* When, Then */
+ runBlocking {
+ assertThrows {
+ mock.builderMethod()
+ }
+ }
+ }
+
+ @Test
+ fun `should throw exception instances on consecutive suspendable methodCalls`() {
+ /* Given */
+ val mock = mock {
+ onBlocking { builderMethod() }.doThrow(
+ IllegalArgumentException(),
+ UnsupportedOperationException()
+ )
+ }
+
+ /* When, Then */
+ runBlocking {
+ assertThrows {
+ mock.builderMethod()
+ }
+ assertThrows {
+ mock.builderMethod()
+ }
+ }
+ }
+
+ @Test
+ fun `should throw exception classes on consecutive suspendable methodCalls`() {
+ /* Given */
+ val mock = mock {
+ onBlocking { builderMethod() }.doThrow(
+ IllegalArgumentException::class,
+ UnsupportedOperationException::class
+ )
+ }
+
+ /* When, Then */
+ runBlocking {
+ assertThrows {
+ mock.builderMethod()
+ }
+ assertThrows {
+ mock.builderMethod()
+ }
+ }
+ }
+
+ @Test
+ fun `should mock suspendable methodCall with result from answer instance`() {
+ /* Given */
+ val answer: Answer = Answer { "result" }
+ val mock = mock {
+ onBlocking { stringResult() } doAnswer answer
+ }
+
+ /* When */
+
+ val result = runBlocking { mock.stringResult() }
+
+ /* Then */
+ expect(result).toBe("result")
+ }
+
+ @Test
+ fun `should mock suspendable methodCall with result from lambda`() {
+ /* Given */
+ val mock = mock {
+ onBlocking { stringResult() } doAnswer { "result" }
+ }
+
+ /* When */
+ val result = runBlocking { mock.stringResult() }
+
+ /* Then */
+ expect(result).toBe("result")
+ }
+
+ @Test
+ fun `should mock suspendable methodCall with result from lambda with argument`() {
+ /* Given */
+ val mock = mock {
+ onBlocking { stringResult(any()) } doAnswer { "${it.arguments[0]}-result" }
+ }
+
+ /* When */
+ val result = runBlocking { mock.stringResult("argument") }
+
+ /* Then */
+ expect(result).toBe("argument-result")
+ }
+
+ @Test
+ fun `should mock suspendable methodCall with result from lambda with deconstructed argument`() {
+ /* Given */
+ val mock = mock {
+ onBlocking { stringResult(any()) } doAnswer { (s: String) -> "$s-result" }
+ }
+
+ /* When */
+ val result = runBlocking { mock.stringResult("argument") }
+
+ /* Then */
+ expect(result).toBe("argument-result")
+ }
+
+ @Test
+ fun `should mock suspendable methodCall with result from lambda with deconstructed arguments`() {
+ /* Given */
+ val mock = mock {
+ onBlocking { stringResult(any(), any()) } doAnswer { (a: String, b: String) ->
+ "$a + $b"
+ }
+ }
+
+ /* When */
+ val result = runBlocking { mock.stringResult("apple", "banana") }
+
+ /* Then */
+ expect(result).toBe("apple + banana")
+ }
+
+ @Test
+ fun `should mock suspendable methodCall with value class result`() {
+ /* Given */
+ val valueClass = ValueClass("A")
+ val mock = mock {
+ onBlocking { valueClassResult() } doReturn valueClass
+ }
+
+ /* When */
+ val result: ValueClass = runBlocking { mock.valueClassResult() }
+
+ /* Then */
+ expect(result).toBe(valueClass)
+ }
+
+ @Test
+ fun `should mock suspendable methodCall with nullable value class result`() {
+ /* Given */
+ val valueClass = ValueClass("A")
+ val mock = mock {
+ onBlocking { nullableValueClassResult() } doReturn valueClass
+ }
+
+ /* When */
+ val result: ValueClass? = runBlocking { mock.nullableValueClassResult() }
+
+ /* Then */
+ expect(result).toBe(valueClass)
+ }
+
+ @Test
+ fun `should mock consecutive suspendable methodCall with value class results`() {
+ /* Given */
+ val valueClassA = ValueClass("A")
+ val valueClassB = ValueClass("B")
+ val mock = mock {
+ onBlocking { valueClassResult() }.doReturnConsecutively(valueClassA, valueClassB)
+ }
+
+ /* When */
+ val (result1, result2) = runBlocking {
+ mock.valueClassResult() to mock.valueClassResult()
+ }
+
+ /* Then */
+ expect(result1).toBe(valueClassA)
+ expect(result2).toBe(valueClassB)
+ }
+
+ @Test
+ fun `should mock suspendable methodCall with nested value class result`() {
+ /* Given */
+ val nestedValueClass = NestedValueClass(ValueClass("A"))
+ val mock = mock {
+ onBlocking { nestedValueClassResult() } doReturn nestedValueClass
+ }
+
+ /* When */
+ val result: NestedValueClass = runBlocking { mock.nestedValueClassResult() }
+
+ /* Then */
+ expect(result).toBe(nestedValueClass)
+ expect(result.value).toBe(nestedValueClass.value)
+ }
+}
diff --git a/tests/src/test/kotlin/test/CoroutinesTest.kt b/tests/src/test/kotlin/test/CoroutinesTest.kt
new file mode 100644
index 00000000..336a032d
--- /dev/null
+++ b/tests/src/test/kotlin/test/CoroutinesTest.kt
@@ -0,0 +1,513 @@
+//@file:Suppress("EXPERIMENTAL_FEATURE_WARNING")
+
+package test
+
+import com.nhaarman.expect.expect
+import kotlinx.coroutines.*
+import kotlinx.coroutines.channels.actor
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.mockito.InOrder
+import org.mockito.kotlin.*
+import org.mockito.kotlin.doReturn
+import org.mockito.kotlin.inOrder
+import java.util.*
+
+class CoroutinesTest {
+
+ @Test
+ fun stubbingSuspending() {
+ /* Given */
+ val mock = mock {
+ onBlocking { stringResult() } doReturn "Value"
+ }
+
+ /* When */
+ val result = runBlocking { mock.stringResult() }
+
+ /* Then */
+ expect(result).toBe("Value")
+ }
+
+ @Test
+ fun stubbingSuspending_usingSuspendingFunction() {
+ /* Given */
+ val mock = mock {
+ onBlocking { stringResult() } doReturn runBlocking { SomeClass().result("Value") }
+ }
+
+ /* When */
+ val result = runBlocking { mock.stringResult() }
+
+ /* Then */
+ expect(result).toBe("Value")
+ }
+
+ @Test
+ fun stubbingSuspending_runBlocking() = runBlocking {
+ /* Given */
+ val mock = mock {
+ onBlocking { stringResult() } doReturn "Value"
+ }
+
+ /* When */
+ val result = mock.stringResult()
+
+ /* Then */
+ expect(result).toBe("Value")
+ }
+
+ @Test
+ fun stubbingSuspending_wheneverBlocking() {
+ /* Given */
+ val mock: SuspendFunctions = mock()
+ wheneverBlocking { mock.stringResult() }
+ .doReturn("Value")
+
+ /* When */
+ val result = runBlocking { mock.stringResult() }
+
+ /* Then */
+ expect(result).toBe("Value")
+ }
+
+ @Test
+ fun stubbingSuspending_doReturn() {
+ /* Given */
+ val mock = spy(SomeClass())
+ doReturn("Value").wheneverBlocking(mock) { delaying() }
+
+ /* When */
+ val result = runBlocking { mock.delaying() }
+
+ /* Then */
+ expect(result).toBe("Value")
+ }
+
+ @Test
+ fun stubbingNonSuspending() {
+ /* Given */
+ val mock = mock {
+ onBlocking { stringResult() } doReturn "Value"
+ }
+
+ /* When */
+ val result = mock.stringResult()
+
+ /* Then */
+ expect(result).toBe("Value")
+ }
+
+ @Test
+ fun stubbingNonSuspending_runBlocking() = runBlocking {
+ /* Given */
+ val mock = mock {
+ onBlocking { stringResult() } doReturn "Value"
+ }
+
+ /* When */
+ val result = mock.stringResult()
+
+ /* Then */
+ expect(result).toBe("Value")
+ }
+
+ @Test
+ fun delayingResult() {
+ /* Given */
+ val instance = SomeClass()
+
+ /* When */
+ val result = runBlocking { instance.delaying() }
+
+ /* Then */
+ expect(result).toBe("Value")
+ }
+
+ @Test
+ fun delayingResult_runBlocking() = runBlocking {
+ /* Given */
+ val instance = SomeClass()
+
+ /* When */
+ val result = instance.delaying()
+
+ /* Then */
+ expect(result).toBe("Value")
+ }
+
+ @Test
+ fun verifySuspendFunctionCalled() {
+ /* Given */
+ val mock = mock()
+
+ /* When */
+ runBlocking { mock.stringResult() }
+
+ /* Then */
+ runBlocking { verify(mock).stringResult() }
+ }
+
+ @Test
+ fun verifySuspendFunctionCalled_runBlocking() = runBlocking {
+ val mock = mock()
+
+ mock.stringResult()
+
+ verify(mock).stringResult()
+ }
+
+ @Test
+ fun verifySuspendFunctionCalled_verifyBlocking() {
+ val mock = mock()
+
+ runBlocking { mock.stringResult() }
+
+ verifyBlocking(mock) { stringResult() }
+ }
+
+ @Test
+ fun verifyAtLeastOnceSuspendFunctionCalled_verifyBlocking() {
+ val mock = mock()
+
+ runBlocking { mock.stringResult() }
+ runBlocking { mock.stringResult() }
+
+ verifyBlocking(mock, atLeastOnce()) { stringResult() }
+ }
+
+ @Test
+ fun verifySuspendMethod() = runBlocking {
+ val mock: SuspendFunctions = mock()
+
+ mock.stringResult()
+
+ inOrder(mock) {
+ verify(mock).stringResult()
+ }
+ }
+
+ @Test
+ fun answerWithSuspendFunction() = runBlocking {
+ val mock: SuspendFunctions = mock()
+
+ wheneverBlocking {mock.stringResult(any()) } doAnswer { it.single() }
+
+ assertEquals("Value", mock.stringResult("Value"))
+ }
+
+ @Test
+ fun inplaceAnswerWithSuspendFunction() = runBlocking {
+ val mock: SuspendFunctions = mock {
+ onBlocking { stringResult(any()) } doAnswer { it.single() }
+ }
+
+ assertEquals("Value", mock.stringResult("Value"))
+ }
+
+ @Test
+ fun callFromSuspendFunction() = runBlocking {
+ val mock: SuspendFunctions = mock()
+
+ wheneverBlocking {mock.stringResult(any()) } doAnswer { it.single() }
+
+ val result = async {
+ val answer = mock.stringResult("Value")
+
+ Result.success(answer)
+ }
+
+ assertEquals("Value", result.await().getOrThrow())
+ }
+
+ @Test
+ @OptIn(ObsoleteCoroutinesApi::class)
+ fun callFromActor() = runBlocking {
+ val mock: SuspendFunctions = mock()
+
+ wheneverBlocking { mock.stringResult(any()) } doAnswer { it.single() }
+
+ val actor = actor> {
+ for (element in channel) {
+ mock.stringResult(element.get())
+ }
+ }
+
+ actor.send(Optional.of("Value"))
+ actor.close()
+
+ verify(mock).stringResult("Value")
+
+ Unit
+ }
+
+ @Test
+ fun answerWithSuspendFunctionWithoutArgs() = runBlocking {
+ val mock: SuspendFunctions = mock()
+
+ wheneverBlocking { mock.stringResult() } doReturn "Value"
+
+ assertEquals("Value", mock.stringResult())
+ }
+
+ @Test
+ fun answerWithSuspendFunctionWithDestructuredArgs() = runBlocking {
+ val mock: SuspendFunctions = mock()
+
+ wheneverBlocking { mock.stringResult(any()) } doAnswer { (s: String) -> s }
+
+ assertEquals("Value", mock.stringResult("Value"))
+ }
+
+ @Test
+ fun willAnswerWithControlledSuspend() = runBlocking {
+ val mock: SuspendFunctions = mock()
+
+ val job = Job()
+
+ wheneverBlocking { mock.stringResult() } doAnswer {
+ job.join()
+ "Value"
+ }
+
+ val asyncTask = async {
+ mock.stringResult()
+ }
+
+ job.complete()
+
+ withTimeout(100) {
+ assertEquals("Value", asyncTask.await())
+ }
+ }
+
+ @Test
+ fun stubberAnswerWithSuspendFunction() = runBlocking {
+ val mock: SuspendFunctions = mock()
+
+ doSuspendableAnswer { it.single() }.whenever(mock).stringResult(any())
+
+ assertEquals("Value", mock.stringResult("Value"))
+ }
+
+ @Test
+ fun stubberCallFromSuspendFunction() = runBlocking {
+ val mock: SuspendFunctions = mock()
+
+ doSuspendableAnswer { it.single() }.whenever(mock).stringResult(any())
+
+ val result = async {
+ val answer = mock.stringResult("Value")
+
+ Result.success(answer)
+ }
+
+ assertEquals("Value", result.await().getOrThrow())
+ }
+
+ @Test
+ @OptIn(ObsoleteCoroutinesApi::class)
+ fun stubberCallFromActor() {
+ val mock: SuspendFunctions = mock()
+
+ doSuspendableAnswer {
+ withContext(Dispatchers.Default) { it.single() }
+ }.wheneverBlocking(mock) { stringResult(any()) }
+
+ runBlocking {
+ val actor = actor> {
+ for (element in channel) {
+ mock.stringResult(element.get())
+ }
+ }
+
+ actor.send(Optional.of("Value"))
+ actor.close()
+ }
+
+ verifyBlocking(mock) {stringResult("Value") }
+ }
+
+ @Test
+ fun stubberAnswerWithSuspendFunctionWithoutArgs() {
+ val mock: SuspendFunctions = mock()
+
+ doSuspendableAnswer {
+ withContext(Dispatchers.Default) { "Value" }
+ }.wheneverBlocking(mock) { stringResult() }
+
+ assertEquals("Value", runBlocking { mock.stringResult() })
+ }
+
+ @Test
+ fun stubberAnswerWithSuspendFunctionWithDestructuredArgs() {
+ val mock: SuspendFunctions = mock()
+
+ doSuspendableAnswer { (s: String) ->
+ withContext(Dispatchers.Default) { s }
+ }.wheneverBlocking(mock) { stringResult(any()) }
+
+ assertEquals("Value", runBlocking {mock.stringResult("Value") })
+ }
+
+ @Test
+ fun stubberWillAnswerWithControlledSuspend() {
+ val mock: SuspendFunctions = mock()
+
+ val job = Job()
+
+ doSuspendableAnswer {
+ job.join()
+ "Value"
+ }.wheneverBlocking(mock) { stringResult() }
+
+ runBlocking {
+ val asyncTask = async {
+ mock.stringResult()
+ }
+
+ job.complete()
+
+ withTimeout(100) {
+ assertEquals("Value", asyncTask.await())
+ }
+ }
+ }
+
+ @Test
+ fun inOrderRemainsCompatible() {
+ /* Given */
+ val mock: SuspendFunctions = mock()
+
+ /* When */
+ val inOrder = inOrder(mock)
+
+ /* Then */
+ expect(inOrder).toBeInstanceOf()
+ }
+
+ @Test
+ fun inOrderSuspendingCalls() {
+ /* Given */
+ val fixtureOne: SuspendFunctions = mock()
+ val fixtureTwo: SuspendFunctions = mock()
+
+ /* When */
+ runBlocking {
+ fixtureOne.stringResult()
+ fixtureTwo.stringResult()
+ }
+
+ /* Then */
+ val inOrder = inOrder(fixtureOne, fixtureTwo)
+ inOrder.verifyBlocking(fixtureOne) { stringResult() }
+ inOrder.verifyBlocking(fixtureTwo) { stringResult() }
+ }
+
+ @Test
+ fun inOrderSuspendingCallsFailure() {
+ /* Given */
+ val fixtureOne: SuspendFunctions = mock()
+ val fixtureTwo: SuspendFunctions = mock()
+
+ /* When */
+ runBlocking {
+ fixtureOne.stringResult()
+ fixtureTwo.stringResult()
+ }
+
+ /* Then */
+ val inOrder = inOrder(fixtureOne, fixtureTwo)
+ inOrder.verifyBlocking(fixtureTwo) { stringResult() }
+ assertThrows(AssertionError::class.java) {
+ inOrder.verifyBlocking(fixtureOne) { stringResult() }
+ }
+ }
+
+ @Test
+ fun inOrderBlockSuspendingCalls() {
+ /* Given */
+ val fixtureOne: SuspendFunctions = mock()
+ val fixtureTwo: SuspendFunctions = mock()
+
+ /* When */
+ runBlocking {
+ fixtureOne.stringResult()
+ fixtureTwo.stringResult()
+ }
+
+ /* Then */
+ inOrder(fixtureOne, fixtureTwo) {
+ verifyBlocking(fixtureOne) { stringResult() }
+ verifyBlocking(fixtureTwo) { stringResult() }
+ }
+ }
+
+ @Test
+ fun inOrderBlockSuspendingCallsFailure() {
+ /* Given */
+ val fixtureOne: SuspendFunctions = mock()
+ val fixtureTwo: SuspendFunctions = mock()
+
+ /* When */
+ runBlocking {
+ fixtureOne.stringResult()
+ fixtureTwo.stringResult()
+ }
+
+ /* Then */
+ inOrder(fixtureOne, fixtureTwo) {
+ verifyBlocking(fixtureTwo) { stringResult() }
+ assertThrows(AssertionError::class.java) {
+ verifyBlocking(fixtureOne) { stringResult() }
+ }
+ }
+ }
+
+ @Test
+ fun inOrderOnObjectSuspendingCalls() {
+ /* Given */
+ val mock: SuspendFunctions = mock()
+
+ /* When */
+ runBlocking {
+ mock.stringResult("Value")
+ mock.stringResult("Other Value")
+ }
+
+ /* Then */
+ mock.inOrder {
+ verifyBlocking { stringResult("Value") }
+ verifyBlocking { stringResult("Other Value") }
+ }
+ }
+
+ @Test
+ fun inOrderOnObjectSuspendingCallsFailure() {
+ /* Given */
+ val mock: SuspendFunctions = mock()
+
+ /* When */
+ runBlocking {
+ mock.stringResult("Value")
+ mock.stringResult("Other Value")
+ }
+
+ /* Then */
+ mock.inOrder {
+ verifyBlocking { stringResult("Other Value") }
+ assertThrows(AssertionError::class.java) {
+ verifyBlocking { stringResult("Value") }
+ }
+ }
+ }
+}
+
+open class SomeClass {
+ suspend fun result(r: String) = withContext(Dispatchers.Default) { r }
+ open suspend fun delaying() = withContext(Dispatchers.Default) {
+ delay(100)
+ "Value"
+ }
+}
diff --git a/tests/src/test/kotlin/test/JUnit4Utils.kt b/tests/src/test/kotlin/test/JUnit4Utils.kt
new file mode 100644
index 00000000..4e6b053f
--- /dev/null
+++ b/tests/src/test/kotlin/test/JUnit4Utils.kt
@@ -0,0 +1,13 @@
+package test
+
+import junit.framework.AssertionFailedError
+
+/** Inspired by JUnit5, asserts an exception being thrown */
+inline fun assertThrows(block: () -> Unit): E {
+ return try {
+ block.invoke()
+ throw AssertionFailedError("No exception of type ${(E::class).simpleName} was thrown")
+ } catch (e: Throwable) {
+ e as? E ?: throw e
+ }
+}
diff --git a/tests/src/test/kotlin/test/MatchersTest.kt b/tests/src/test/kotlin/test/MatchersTest.kt
index 9fe1eeea..eb87562d 100644
--- a/tests/src/test/kotlin/test/MatchersTest.kt
+++ b/tests/src/test/kotlin/test/MatchersTest.kt
@@ -17,7 +17,7 @@ class MatchersTest : TestBase() {
class AnyMatchersTest {
@Test
fun anyString() {
- mock().apply {
+ mock().apply {
string("")
verify(this).string(any())
}
@@ -25,7 +25,7 @@ class MatchersTest : TestBase() {
@Test
fun anyNullableString() {
- mock().apply {
+ mock().apply {
nullableString("")
verify(this).nullableString(any())
}
@@ -33,7 +33,7 @@ class MatchersTest : TestBase() {
@Test
fun anyBoolean() {
- mock().apply {
+ mock