Skip to content

Commit

Permalink
Add convenience methods for stubbing suspending functions (#470)
Browse files Browse the repository at this point in the history
  • Loading branch information
Rajin9601 authored Dec 18, 2022
1 parent 7793ba4 commit 513056e
Show file tree
Hide file tree
Showing 5 changed files with 113 additions and 4 deletions.
21 changes: 21 additions & 0 deletions mockito-kotlin/src/main/kotlin/org/mockito/kotlin/BDDMockito.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -46,13 49,31 @@ fun <T> given(methodCall: () -> T): BDDMyOngoingStubbing<T> {
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 <T> givenBlocking(methodCall: suspend CoroutineScope.() -> T): BDDMockito.BDDMyOngoingStubbing<T> {
return runBlocking { BDDMockito.given(methodCall()) }
}

/**
* Alias for [BDDMockito.then].
*/
fun <T> then(mock: T): BDDMockito.Then<T> {
return BDDMockito.then(mock)
}

/**
* Alias for [Then.should], with suspending lambda.
*/
fun <T, R> Then<T>.shouldBlocking(f: suspend T.() -> R): R {
val m = should()
return runBlocking { m.f() }
}

/**
* Alias for [BDDMyOngoingStubbing.will]
* */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -43,6 45,16 @@ inline fun <T> whenever(methodCall: T): OngoingStubbing<T> {
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 <T> wheneverBlocking(methodCall: suspend CoroutineScope.() -> T): OngoingStubbing<T> {
return runBlocking { Mockito.`when`(methodCall()) }
}

/**
* Sets a return value to be returned when the method is called.
*
Expand Down
14 changes: 13 additions & 1 deletion mockito-kotlin/src/main/kotlin/org/mockito/kotlin/Stubber.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -62,4 63,15 @@ fun doThrow(vararg toBeThrown: Throwable): Stubber {
return Mockito.doThrow(*toBeThrown)!!
}

fun <T> Stubber.whenever(mock: T) = `when`(mock)
fun <T> 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 <T> Stubber.wheneverBlocking(mock: T, f: suspend T.() -> Unit) {
val m = whenever(mock)
runBlocking { m.f() }
}
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 23,7 @@ class BDDMockitoKtTest {
}

@Test
fun willSuspendableAnswer_witArgument() = runBlocking {
fun willSuspendableAnswer_withArgument() = runBlocking {
val fixture: SomeInterface = mock()

given(fixture.suspendingWithArg(any())).willSuspendableAnswer {
Expand All @@ -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<Int>(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()
Expand Down
34 changes: 32 additions & 2 deletions mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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 */
Expand Down Expand Up @@ -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
}
Expand Down

0 comments on commit 513056e

Please sign in to comment.