diff --git a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/BDDMockito.kt b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/BDDMockito.kt index 9363b1e8..3728b1bc 100644 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/BDDMockito.kt +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/BDDMockito.kt @@ -25,8 +25,11 @@ package org.mockito.kotlin +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.runBlocking import org.mockito.BDDMockito import org.mockito.BDDMockito.BDDMyOngoingStubbing +import org.mockito.BDDMockito.Then import org.mockito.invocation.InvocationOnMock import org.mockito.kotlin.internal.SuspendableAnswer import org.mockito.stubbing.Answer @@ -46,6 +49,16 @@ fun given(methodCall: () -> T): BDDMyOngoingStubbing { return given(methodCall()) } +/** + * Alias for [BDDMockito.given] with a suspending lambda + * + * Warning: Only last method call can be stubbed in the function. + * other method calls are ignored! + */ +fun givenBlocking(methodCall: suspend CoroutineScope.() -> T): BDDMockito.BDDMyOngoingStubbing { + return runBlocking { BDDMockito.given(methodCall()) } +} + /** * Alias for [BDDMockito.then]. */ @@ -53,6 +66,14 @@ fun then(mock: T): BDDMockito.Then { return BDDMockito.then(mock) } +/** + * Alias for [Then.should], with suspending lambda. + */ +fun Then.shouldBlocking(f: suspend T.() -> R): R { + val m = should() + return runBlocking { m.f() } +} + /** * Alias for [BDDMyOngoingStubbing.will] * */ 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 d259a8fa..25761122 100644 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/OngoingStubbing.kt +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/OngoingStubbing.kt @@ -25,6 +25,8 @@ package org.mockito.kotlin +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.runBlocking import org.mockito.Mockito import org.mockito.invocation.InvocationOnMock import org.mockito.kotlin.internal.SuspendableAnswer @@ -43,6 +45,16 @@ 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. * 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 fb417fcd..5fa48c96 100644 --- a/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Stubber.kt +++ b/mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Stubber.kt @@ -25,6 +25,7 @@ package org.mockito.kotlin +import kotlinx.coroutines.runBlocking import org.mockito.Mockito import org.mockito.invocation.InvocationOnMock import org.mockito.stubbing.Stubber @@ -62,4 +63,15 @@ fun doThrow(vararg toBeThrown: Throwable): Stubber { return Mockito.doThrow(*toBeThrown)!! } -fun Stubber.whenever(mock: T) = `when`(mock) \ No newline at end of file +fun Stubber.whenever(mock: T) = `when`(mock) + +/** + * Alias for when with suspending function + * + * Warning: Only one method call can be stubbed in the function. + * Subsequent method calls are ignored! + */ +fun Stubber.wheneverBlocking(mock: T, f: suspend T.() -> Unit) { + val m = whenever(mock) + runBlocking { m.f() } +} diff --git a/mockito-kotlin/src/test/kotlin/org/mockito/kotlin/BDDMockitoKtTest.kt b/mockito-kotlin/src/test/kotlin/org/mockito/kotlin/BDDMockitoKtTest.kt index 0686efe4..b745022f 100644 --- a/mockito-kotlin/src/test/kotlin/org/mockito/kotlin/BDDMockitoKtTest.kt +++ b/mockito-kotlin/src/test/kotlin/org/mockito/kotlin/BDDMockitoKtTest.kt @@ -23,7 +23,7 @@ class BDDMockitoKtTest { } @Test - fun willSuspendableAnswer_witArgument() = runBlocking { + fun willSuspendableAnswer_withArgument() = runBlocking { val fixture: SomeInterface = mock() given(fixture.suspendingWithArg(any())).willSuspendableAnswer { @@ -35,6 +35,40 @@ class BDDMockitoKtTest { Unit } + @Test + fun willSuspendableAnswer_givenBlocking() { + val fixture: SomeInterface = mock() + + givenBlocking { fixture.suspending() }.willSuspendableAnswer { + withContext(Dispatchers.Default) { 42 } + } + + val result = runBlocking { + fixture.suspending() + } + + assertEquals(42, result) + then(fixture).shouldBlocking { suspending() } + Unit + } + + @Test + fun willSuspendableAnswer_givenBlocking_withArgument() { + val fixture: SomeInterface = mock() + + givenBlocking { fixture.suspendingWithArg(any()) }.willSuspendableAnswer { + withContext(Dispatchers.Default) { it.getArgument(0) } + } + + val result = runBlocking { + fixture.suspendingWithArg(42) + } + + assertEquals(42, result) + then(fixture).shouldBlocking { suspendingWithArg(42) } + Unit + } + @Test fun willThrow_kclass_single() { val fixture: SomeInterface = mock() diff --git a/mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt b/mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt index c43d4269..48208133 100644 --- a/mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt +++ b/mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt @@ -61,6 +61,36 @@ class CoroutinesTest { 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 */ @@ -394,11 +424,11 @@ interface SomeInterface { fun nonsuspending(): Int } -class SomeClass { +open class SomeClass { suspend fun result(r: Int) = withContext(Dispatchers.Default) { r } - suspend fun delaying() = withContext(Dispatchers.Default) { + open suspend fun delaying() = withContext(Dispatchers.Default) { delay(100) 42 }