Skip to content

Commit 513056e

Browse files
authored
Add convenience methods for stubbing suspending functions (#470)
1 parent 7793ba4 commit 513056e

File tree

5 files changed

+113
-4
lines changed

5 files changed

+113
-4
lines changed

mockito-kotlin/src/main/kotlin/org/mockito/kotlin/BDDMockito.kt

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,11 @@
2525

2626
package org.mockito.kotlin
2727

28+
import kotlinx.coroutines.CoroutineScope
29+
import kotlinx.coroutines.runBlocking
2830
import org.mockito.BDDMockito
2931
import org.mockito.BDDMockito.BDDMyOngoingStubbing
32+
import org.mockito.BDDMockito.Then
3033
import org.mockito.invocation.InvocationOnMock
3134
import org.mockito.kotlin.internal.SuspendableAnswer
3235
import org.mockito.stubbing.Answer
@@ -46,13 +49,31 @@ fun <T> given(methodCall: () -> T): BDDMyOngoingStubbing<T> {
4649
return given(methodCall())
4750
}
4851

52+
/**
53+
* Alias for [BDDMockito.given] with a suspending lambda
54+
*
55+
* Warning: Only last method call can be stubbed in the function.
56+
* other method calls are ignored!
57+
*/
58+
fun <T> givenBlocking(methodCall: suspend CoroutineScope.() -> T): BDDMockito.BDDMyOngoingStubbing<T> {
59+
return runBlocking { BDDMockito.given(methodCall()) }
60+
}
61+
4962
/**
5063
* Alias for [BDDMockito.then].
5164
*/
5265
fun <T> then(mock: T): BDDMockito.Then<T> {
5366
return BDDMockito.then(mock)
5467
}
5568

69+
/**
70+
* Alias for [Then.should], with suspending lambda.
71+
*/
72+
fun <T, R> Then<T>.shouldBlocking(f: suspend T.() -> R): R {
73+
val m = should()
74+
return runBlocking { m.f() }
75+
}
76+
5677
/**
5778
* Alias for [BDDMyOngoingStubbing.will]
5879
* */

mockito-kotlin/src/main/kotlin/org/mockito/kotlin/OngoingStubbing.kt

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@
2525

2626
package org.mockito.kotlin
2727

28+
import kotlinx.coroutines.CoroutineScope
29+
import kotlinx.coroutines.runBlocking
2830
import org.mockito.Mockito
2931
import org.mockito.invocation.InvocationOnMock
3032
import org.mockito.kotlin.internal.SuspendableAnswer
@@ -43,6 +45,16 @@ inline fun <T> whenever(methodCall: T): OngoingStubbing<T> {
4345
return Mockito.`when`(methodCall)!!
4446
}
4547

48+
/**
49+
* Enables stubbing suspending methods. Use it when you want the mock to return particular value when particular suspending method is called.
50+
*
51+
* Warning: Only one method call can be stubbed in the function.
52+
* other method calls are ignored!
53+
*/
54+
fun <T> wheneverBlocking(methodCall: suspend CoroutineScope.() -> T): OngoingStubbing<T> {
55+
return runBlocking { Mockito.`when`(methodCall()) }
56+
}
57+
4658
/**
4759
* Sets a return value to be returned when the method is called.
4860
*

mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Stubber.kt

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525

2626
package org.mockito.kotlin
2727

28+
import kotlinx.coroutines.runBlocking
2829
import org.mockito.Mockito
2930
import org.mockito.invocation.InvocationOnMock
3031
import org.mockito.stubbing.Stubber
@@ -62,4 +63,15 @@ fun doThrow(vararg toBeThrown: Throwable): Stubber {
6263
return Mockito.doThrow(*toBeThrown)!!
6364
}
6465

65-
fun <T> Stubber.whenever(mock: T) = `when`(mock)
66+
fun <T> Stubber.whenever(mock: T) = `when`(mock)
67+
68+
/**
69+
* Alias for when with suspending function
70+
*
71+
* Warning: Only one method call can be stubbed in the function.
72+
* Subsequent method calls are ignored!
73+
*/
74+
fun <T> Stubber.wheneverBlocking(mock: T, f: suspend T.() -> Unit) {
75+
val m = whenever(mock)
76+
runBlocking { m.f() }
77+
}

mockito-kotlin/src/test/kotlin/org/mockito/kotlin/BDDMockitoKtTest.kt

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ class BDDMockitoKtTest {
2323
}
2424

2525
@Test
26-
fun willSuspendableAnswer_witArgument() = runBlocking {
26+
fun willSuspendableAnswer_withArgument() = runBlocking {
2727
val fixture: SomeInterface = mock()
2828

2929
given(fixture.suspendingWithArg(any())).willSuspendableAnswer {
@@ -35,6 +35,40 @@ class BDDMockitoKtTest {
3535
Unit
3636
}
3737

38+
@Test
39+
fun willSuspendableAnswer_givenBlocking() {
40+
val fixture: SomeInterface = mock()
41+
42+
givenBlocking { fixture.suspending() }.willSuspendableAnswer {
43+
withContext(Dispatchers.Default) { 42 }
44+
}
45+
46+
val result = runBlocking {
47+
fixture.suspending()
48+
}
49+
50+
assertEquals(42, result)
51+
then(fixture).shouldBlocking { suspending() }
52+
Unit
53+
}
54+
55+
@Test
56+
fun willSuspendableAnswer_givenBlocking_withArgument() {
57+
val fixture: SomeInterface = mock()
58+
59+
givenBlocking { fixture.suspendingWithArg(any()) }.willSuspendableAnswer {
60+
withContext(Dispatchers.Default) { it.getArgument<Int>(0) }
61+
}
62+
63+
val result = runBlocking {
64+
fixture.suspendingWithArg(42)
65+
}
66+
67+
assertEquals(42, result)
68+
then(fixture).shouldBlocking { suspendingWithArg(42) }
69+
Unit
70+
}
71+
3872
@Test
3973
fun willThrow_kclass_single() {
4074
val fixture: SomeInterface = mock()

mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,36 @@ class CoroutinesTest {
6161
expect(result).toBe(42)
6262
}
6363

64+
@Test
65+
fun stubbingSuspending_wheneverBlocking() {
66+
/* Given */
67+
val m: SomeInterface = mock()
68+
wheneverBlocking { m.suspending() }
69+
.doReturn(42)
70+
71+
/* When */
72+
val result = runBlocking { m.suspending() }
73+
74+
/* Then */
75+
expect(result).toBe(42)
76+
}
77+
78+
@Test
79+
fun stubbingSuspending_doReturn() {
80+
/* Given */
81+
val m = spy(SomeClass())
82+
doReturn(10)
83+
.wheneverBlocking(m) {
84+
delaying()
85+
}
86+
87+
/* When */
88+
val result = runBlocking { m.delaying() }
89+
90+
/* Then */
91+
expect(result).toBe(10)
92+
}
93+
6494
@Test
6595
fun stubbingNonSuspending() {
6696
/* Given */
@@ -394,11 +424,11 @@ interface SomeInterface {
394424
fun nonsuspending(): Int
395425
}
396426

397-
class SomeClass {
427+
open class SomeClass {
398428

399429
suspend fun result(r: Int) = withContext(Dispatchers.Default) { r }
400430

401-
suspend fun delaying() = withContext(Dispatchers.Default) {
431+
open suspend fun delaying() = withContext(Dispatchers.Default) {
402432
delay(100)
403433
42
404434
}

0 commit comments

Comments
 (0)