Skip to content

Commit 06a78c5

Browse files
uOOOOLeoColman
authored andcommitted
Fix not working test if coroutineTestScope = true (#26)
1 parent 59bb088 commit 06a78c5

File tree

4 files changed

+68
-21
lines changed

4 files changed

+68
-21
lines changed

kotest-extensions-android/kotest-extensions-android-tests/build.gradle.kts

+1-1
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,10 @@ dependencies {
4040
implementation("androidx.test:core-ktx:1.5.0")
4141
testImplementation(project(":kotest-extensions-android"))
4242
testImplementation("io.kotest:kotest-runner-junit5:5.9.1")
43+
testImplementation("org.robolectric:robolectric:4.12.2")
4344
androidTestImplementation("androidx.test:runner:1.5.2")
4445
androidTestImplementation("androidx.test:core:1.5.0")
4546
androidTestImplementation("androidx.test:rules:1.5.0")
46-
4747
}
4848

4949
tasks.withType<Test> {

kotest-extensions-android/kotest-extensions-android-tests/src/test/kotlin/br/com/colman/kotest/android/extensions/ContainedRobolectricTest.kt

+58-1
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,25 @@ package br.com.colman.kotest.android.extensions
22

33
import android.app.Application
44
import android.os.Build
5+
import android.os.Handler
6+
import android.os.Looper
57
import androidx.test.core.app.ApplicationProvider
68
import br.com.colman.kotest.TestApplication
79
import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest
10+
import io.kotest.core.coroutines.backgroundScope
811
import io.kotest.core.spec.style.BehaviorSpec
912
import io.kotest.core.spec.style.StringSpec
13+
import io.kotest.core.test.testCoroutineScheduler
14+
import io.kotest.matchers.collections.shouldContain
15+
import io.kotest.matchers.collections.shouldNotBeEmpty
16+
import io.kotest.matchers.equals.shouldBeEqual
1017
import io.kotest.matchers.shouldBe
1118
import io.kotest.matchers.shouldNotBe
19+
import kotlinx.coroutines.ExperimentalCoroutinesApi
20+
import kotlinx.coroutines.flow.MutableSharedFlow
21+
import kotlinx.coroutines.launch
22+
import kotlinx.coroutines.test.UnconfinedTestDispatcher
23+
import org.robolectric.Shadows.shadowOf
1224

1325
private class MockApplication : Application()
1426

@@ -67,8 +79,9 @@ abstract class ContainedRobolectricRunnerMergeOverwriteTest : StringSpec({
6779
@RobolectricTest(sdk = Build.VERSION_CODES.O_MR1)
6880
class ContainedRobolectricRunnerMergeOverwriteO_MR1Test : ContainedRobolectricRunnerMergeOverwriteTest()
6981

82+
@OptIn(ExperimentalStdlibApi::class, ExperimentalCoroutinesApi::class)
7083
@RobolectricTest
71-
class ContainedRobolectricRunnerDefaultApplicationBehaviorSpecTest : BehaviorSpec({
84+
class ContainedRobolectricRunnerBehaviorSpecTest : BehaviorSpec({
7285
Context("Get the Application defined in AndroidManifest.xml") {
7386
Given("A application context") {
7487
val applicationContext = ApplicationProvider.getApplicationContext<Application>()
@@ -82,4 +95,48 @@ class ContainedRobolectricRunnerDefaultApplicationBehaviorSpecTest : BehaviorSpe
8295
}
8396
}
8497
}
98+
99+
coroutineTestScope = true
100+
101+
Context("Collect the flow in TestScope") {
102+
Given("Some numbers and a SharedFlow") {
103+
val numbers = listOf(1, 2, 3)
104+
val sharedFlow = MutableSharedFlow<Int>()
105+
106+
And("Launch to collect the flow") {
107+
val collectedNumbers = mutableListOf<Int>()
108+
backgroundScope.launch(UnconfinedTestDispatcher(testCoroutineScheduler)) {
109+
sharedFlow.collect { collectedNumbers.add(it) }
110+
}
111+
112+
When("Emit the numbers") {
113+
numbers.forEach { launch { sharedFlow.emit(it) } }
114+
115+
testCoroutineScheduler.advanceUntilIdle()
116+
117+
Then("Collected the same numbers") {
118+
collectedNumbers shouldBeEqual numbers
119+
}
120+
}
121+
}
122+
}
123+
}
124+
125+
Context("Unit testing Android Handler using Robolectric") {
126+
Given("An Android Handler") {
127+
val handler = Handler(Looper.getMainLooper())
128+
129+
When("Add a number on the Handler") {
130+
val numbers = mutableListOf<Long>()
131+
val currentTime = System.currentTimeMillis()
132+
handler.post { numbers.add(currentTime) }
133+
134+
shadowOf(Looper.getMainLooper()).idle()
135+
136+
Then("The Number added the list") {
137+
numbers.shouldNotBeEmpty().shouldContain(currentTime)
138+
}
139+
}
140+
}
141+
}
85142
})

kotest-extensions-android/src/main/kotlin/br/com/colman/kotest/android/extensions/robolectric/ContainedRobolectricRunner.kt

+1
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ internal class ContainedRobolectricRunner(
3737
override fun createClassLoaderConfig(method: FrameworkMethod?): InstrumentationConfiguration {
3838
return InstrumentationConfiguration.Builder(super.createClassLoaderConfig(method))
3939
.doNotAcquirePackage("io.kotest")
40+
.doNotAcquirePackage("kotlinx.coroutines")
4041
.build()
4142
}
4243

kotest-extensions-android/src/main/kotlin/br/com/colman/kotest/android/extensions/robolectric/RobolectricExtension.kt

+8-19
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,11 @@ import io.kotest.core.spec.Spec
88
import io.kotest.core.test.TestCase
99
import io.kotest.core.test.TestResult
1010
import io.kotest.core.test.isRootTest
11-
import kotlinx.coroutines.asCoroutineDispatcher
12-
import kotlinx.coroutines.withContext
1311
import org.robolectric.annotation.Config
14-
import org.robolectric.internal.bytecode.Sandbox
12+
import java.util.WeakHashMap
1513
import kotlin.reflect.KClass
1614
import kotlin.reflect.full.findAnnotation
1715
import kotlin.time.Duration
18-
import java.util.WeakHashMap
19-
import java.util.concurrent.ExecutorService
2016

2117
/**
2218
* We override TestCaseExtension to configure the Robolectric environment because TestCase intercept
@@ -113,21 +109,14 @@ class RobolectricExtension : ConstructorExtension, TestCaseExtension {
113109
execute: suspend (TestCase) -> TestResult,
114110
): TestResult {
115111
val containedRobolectricRunner = runnerMap[testCase.spec]!!
116-
// Using sdkEnvironment.executorService to ensure Robolectric's
117-
// looper state doesn't carry over to the next test class.
118-
val executorService =
119-
Sandbox::class.java
120-
.getValue<ExecutorService>(containedRobolectricRunner.sdkEnvironment)
121-
return withContext(executorService.asCoroutineDispatcher()) {
122-
if (testCase.isRootTest()) {
123-
containedRobolectricRunner.containedBefore()
124-
}
125-
val result = execute(testCase)
126-
if (testCase.isRootTest()) {
127-
containedRobolectricRunner.containedAfter()
128-
}
129-
result
112+
if (testCase.isRootTest()) {
113+
containedRobolectricRunner.containedBefore()
114+
}
115+
val result = execute(testCase)
116+
if (testCase.isRootTest()) {
117+
containedRobolectricRunner.containedAfter()
130118
}
119+
return result
131120
}
132121
}
133122

0 commit comments

Comments
 (0)