From 5fa30e172666ddab87054fdcfde1b1f8a64feef6 Mon Sep 17 00:00:00 2001 From: Niek Haarman Date: Sun, 3 Dec 2017 21:34:34 +0100 Subject: [PATCH] Support coroutines --- .gitignore | 2 + mockito-kotlin/build.gradle | 17 ++ .../com/nhaarman/mockitokotlin2/KStubbing.kt | 9 +- .../nhaarman/mockitokotlin2/Verification.kt | 12 ++ .../src/test/kotlin/test/CoroutinesTest.kt | 158 ++++++++++++++++++ settings.gradle | 2 +- 6 files changed, 198 insertions(+), 2 deletions(-) create mode 100644 mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt diff --git a/.gitignore b/.gitignore index 1f606ebd..0a1daff9 100644 --- a/.gitignore +++ b/.gitignore @@ -18,3 +18,5 @@ out/ .idea/uiDesigner.xml .idea/vcs.xml .idea/workspace.xml + +*.orig diff --git a/mockito-kotlin/build.gradle b/mockito-kotlin/build.gradle index e5d1d251..d195990e 100644 --- a/mockito-kotlin/build.gradle +++ b/mockito-kotlin/build.gradle @@ -25,7 +25,17 @@ repositories { dependencies { compileOnly "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + compileOnly 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.19.3' + compile "org.mockito:mockito-core:2.13.0" + + testCompile 'junit:junit:4.12' + testCompile 'com.nhaarman:expect.kt:1.0.0' + + testCompile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + testCompile 'org.jetbrains.kotlinx:kotlinx-coroutines-core:0.19.3' + + testImplementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:0.21" } dokka { @@ -39,3 +49,10 @@ dokka { } } javadoc.dependsOn dokka + + +kotlin { + experimental { + coroutines "enable" + } +} \ No newline at end of file diff --git a/mockito-kotlin/src/main/kotlin/com/nhaarman/mockitokotlin2/KStubbing.kt b/mockito-kotlin/src/main/kotlin/com/nhaarman/mockitokotlin2/KStubbing.kt index 4ed5bf54..14e68e1e 100644 --- a/mockito-kotlin/src/main/kotlin/com/nhaarman/mockitokotlin2/KStubbing.kt +++ b/mockito-kotlin/src/main/kotlin/com/nhaarman/mockitokotlin2/KStubbing.kt @@ -26,6 +26,7 @@ package com.nhaarman.mockitokotlin2 import com.nhaarman.mockitokotlin2.internal.createInstance +import kotlinx.coroutines.experimental.runBlocking import org.mockito.Mockito import org.mockito.stubbing.OngoingStubbing import kotlin.reflect.KClass @@ -42,7 +43,7 @@ inline fun T.stub(stubbing: KStubbing.(T) -> Unit): T { return apply { KStubbing(this).stubbing(this) } } -class KStubbing(private val mock: T) { +class KStubbing(val mock: T) { fun on(methodCall: R): OngoingStubbing = Mockito.`when`(methodCall) @@ -74,4 +75,10 @@ class KStubbing(private val mock: T) { ) } } + + fun KStubbing.onBlocking( + m: suspend T.() -> R + ): OngoingStubbing { + return runBlocking { Mockito.`when`(mock.m()) } + } } \ No newline at end of file diff --git a/mockito-kotlin/src/main/kotlin/com/nhaarman/mockitokotlin2/Verification.kt b/mockito-kotlin/src/main/kotlin/com/nhaarman/mockitokotlin2/Verification.kt index 1db20a61..ae52c9a9 100644 --- a/mockito-kotlin/src/main/kotlin/com/nhaarman/mockitokotlin2/Verification.kt +++ b/mockito-kotlin/src/main/kotlin/com/nhaarman/mockitokotlin2/Verification.kt @@ -26,6 +26,7 @@ package com.nhaarman.mockitokotlin2 import com.nhaarman.mockitokotlin2.internal.createInstance +import kotlinx.coroutines.experimental.runBlocking import org.mockito.InOrder import org.mockito.Mockito import org.mockito.verification.VerificationAfterDelay @@ -41,6 +42,17 @@ fun verify(mock: T): T { return Mockito.verify(mock)!! } +/** + * Verifies certain suspending behavior happened once. + * + * Warning: Only one method call can be verified in the function. + * Subsequent method calls are ignored! + */ +fun verifyBlocking(mock: T, f: suspend T.() -> Unit) { + val m = Mockito.verify(mock) + runBlocking { m.f() } +} + /** * Verifies certain behavior happened at least once / exact number of times / never. * diff --git a/mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt b/mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt new file mode 100644 index 00000000..16118ee2 --- /dev/null +++ b/mockito-kotlin/src/test/kotlin/test/CoroutinesTest.kt @@ -0,0 +1,158 @@ +@file:Suppress("EXPERIMENTAL_FEATURE_WARNING") + +package test + +import com.nhaarman.expect.expect +import com.nhaarman.mockitokotlin2.doReturn +import com.nhaarman.mockitokotlin2.mock +import com.nhaarman.mockitokotlin2.verify +import com.nhaarman.mockitokotlin2.verifyBlocking +import kotlinx.coroutines.experimental.CommonPool +import kotlinx.coroutines.experimental.delay +import kotlinx.coroutines.experimental.runBlocking +import kotlinx.coroutines.experimental.withContext +import org.junit.Test + + +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 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() } + } +} + +interface SomeInterface { + + suspend fun suspending(): Int + fun nonsuspending(): Int +} + +class SomeClass { + + suspend fun result(r: Int) = withContext(CommonPool) { r } + + suspend fun delaying() = withContext(CommonPool) { + delay(100) + 42 + } +} diff --git a/settings.gradle b/settings.gradle index ad6c4a26..5086f47a 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1,2 +1,2 @@ include 'mockito-kotlin' -include 'tests' +include 'tests' \ No newline at end of file