diff --git a/kotest-extensions-android/kotest-extensions-android-tests/build.gradle.kts b/kotest-extensions-android/kotest-extensions-android-tests/build.gradle.kts index 1ba41b8..95afa3d 100644 --- a/kotest-extensions-android/kotest-extensions-android-tests/build.gradle.kts +++ b/kotest-extensions-android/kotest-extensions-android-tests/build.gradle.kts @@ -40,10 +40,10 @@ dependencies { implementation("androidx.test:core-ktx:1.5.0") testImplementation(project(":kotest-extensions-android")) testImplementation("io.kotest:kotest-runner-junit5:5.9.1") + testImplementation("org.robolectric:robolectric:4.12.2") androidTestImplementation("androidx.test:runner:1.5.2") androidTestImplementation("androidx.test:core:1.5.0") androidTestImplementation("androidx.test:rules:1.5.0") - } tasks.withType { diff --git a/kotest-extensions-android/kotest-extensions-android-tests/src/test/kotlin/br/com/colman/kotest/android/extensions/ContainedRobolectricTest.kt b/kotest-extensions-android/kotest-extensions-android-tests/src/test/kotlin/br/com/colman/kotest/android/extensions/ContainedRobolectricTest.kt index 02f5d8e..9164a89 100644 --- a/kotest-extensions-android/kotest-extensions-android-tests/src/test/kotlin/br/com/colman/kotest/android/extensions/ContainedRobolectricTest.kt +++ b/kotest-extensions-android/kotest-extensions-android-tests/src/test/kotlin/br/com/colman/kotest/android/extensions/ContainedRobolectricTest.kt @@ -2,13 +2,25 @@ package br.com.colman.kotest.android.extensions import android.app.Application import android.os.Build +import android.os.Handler +import android.os.Looper import androidx.test.core.app.ApplicationProvider import br.com.colman.kotest.TestApplication import br.com.colman.kotest.android.extensions.robolectric.RobolectricTest +import io.kotest.core.coroutines.backgroundScope import io.kotest.core.spec.style.BehaviorSpec import io.kotest.core.spec.style.StringSpec +import io.kotest.core.test.testCoroutineScheduler +import io.kotest.matchers.collections.shouldContain +import io.kotest.matchers.collections.shouldNotBeEmpty +import io.kotest.matchers.equals.shouldBeEqual import io.kotest.matchers.shouldBe import io.kotest.matchers.shouldNotBe +import kotlinx.coroutines.ExperimentalCoroutinesApi +import kotlinx.coroutines.flow.MutableSharedFlow +import kotlinx.coroutines.launch +import kotlinx.coroutines.test.UnconfinedTestDispatcher +import org.robolectric.Shadows.shadowOf private class MockApplication : Application() @@ -67,8 +79,9 @@ abstract class ContainedRobolectricRunnerMergeOverwriteTest : StringSpec({ @RobolectricTest(sdk = Build.VERSION_CODES.O_MR1) class ContainedRobolectricRunnerMergeOverwriteO_MR1Test : ContainedRobolectricRunnerMergeOverwriteTest() +@OptIn(ExperimentalStdlibApi::class, ExperimentalCoroutinesApi::class) @RobolectricTest -class ContainedRobolectricRunnerDefaultApplicationBehaviorSpecTest : BehaviorSpec({ +class ContainedRobolectricRunnerBehaviorSpecTest : BehaviorSpec({ Context("Get the Application defined in AndroidManifest.xml") { Given("A application context") { val applicationContext = ApplicationProvider.getApplicationContext() @@ -82,4 +95,48 @@ class ContainedRobolectricRunnerDefaultApplicationBehaviorSpecTest : BehaviorSpe } } } + + coroutineTestScope = true + + Context("Collect the flow in TestScope") { + Given("Some numbers and a SharedFlow") { + val numbers = listOf(1, 2, 3) + val sharedFlow = MutableSharedFlow() + + And("Launch to collect the flow") { + val collectedNumbers = mutableListOf() + backgroundScope.launch(UnconfinedTestDispatcher(testCoroutineScheduler)) { + sharedFlow.collect { collectedNumbers.add(it) } + } + + When("Emit the numbers") { + numbers.forEach { launch { sharedFlow.emit(it) } } + + testCoroutineScheduler.advanceUntilIdle() + + Then("Collected the same numbers") { + collectedNumbers shouldBeEqual numbers + } + } + } + } + } + + Context("Unit testing Android Handler using Robolectric") { + Given("An Android Handler") { + val handler = Handler(Looper.getMainLooper()) + + When("Add a number on the Handler") { + val numbers = mutableListOf() + val currentTime = System.currentTimeMillis() + handler.post { numbers.add(currentTime) } + + shadowOf(Looper.getMainLooper()).idle() + + Then("The Number added the list") { + numbers.shouldNotBeEmpty().shouldContain(currentTime) + } + } + } + } }) diff --git a/kotest-extensions-android/src/main/kotlin/br/com/colman/kotest/android/extensions/robolectric/ContainedRobolectricRunner.kt b/kotest-extensions-android/src/main/kotlin/br/com/colman/kotest/android/extensions/robolectric/ContainedRobolectricRunner.kt index 56329c4..0875e46 100644 --- a/kotest-extensions-android/src/main/kotlin/br/com/colman/kotest/android/extensions/robolectric/ContainedRobolectricRunner.kt +++ b/kotest-extensions-android/src/main/kotlin/br/com/colman/kotest/android/extensions/robolectric/ContainedRobolectricRunner.kt @@ -37,6 +37,7 @@ internal class ContainedRobolectricRunner( override fun createClassLoaderConfig(method: FrameworkMethod?): InstrumentationConfiguration { return InstrumentationConfiguration.Builder(super.createClassLoaderConfig(method)) .doNotAcquirePackage("io.kotest") + .doNotAcquirePackage("kotlinx.coroutines") .build() } diff --git a/kotest-extensions-android/src/main/kotlin/br/com/colman/kotest/android/extensions/robolectric/RobolectricExtension.kt b/kotest-extensions-android/src/main/kotlin/br/com/colman/kotest/android/extensions/robolectric/RobolectricExtension.kt index 4ed3e89..b68aaa5 100644 --- a/kotest-extensions-android/src/main/kotlin/br/com/colman/kotest/android/extensions/robolectric/RobolectricExtension.kt +++ b/kotest-extensions-android/src/main/kotlin/br/com/colman/kotest/android/extensions/robolectric/RobolectricExtension.kt @@ -8,15 +8,11 @@ import io.kotest.core.spec.Spec import io.kotest.core.test.TestCase import io.kotest.core.test.TestResult import io.kotest.core.test.isRootTest -import kotlinx.coroutines.asCoroutineDispatcher -import kotlinx.coroutines.withContext import org.robolectric.annotation.Config -import org.robolectric.internal.bytecode.Sandbox +import java.util.WeakHashMap import kotlin.reflect.KClass import kotlin.reflect.full.findAnnotation import kotlin.time.Duration -import java.util.WeakHashMap -import java.util.concurrent.ExecutorService /** * We override TestCaseExtension to configure the Robolectric environment because TestCase intercept @@ -113,21 +109,14 @@ class RobolectricExtension : ConstructorExtension, TestCaseExtension { execute: suspend (TestCase) -> TestResult, ): TestResult { val containedRobolectricRunner = runnerMap[testCase.spec]!! - // Using sdkEnvironment.executorService to ensure Robolectric's - // looper state doesn't carry over to the next test class. - val executorService = - Sandbox::class.java - .getValue(containedRobolectricRunner.sdkEnvironment) - return withContext(executorService.asCoroutineDispatcher()) { - if (testCase.isRootTest()) { - containedRobolectricRunner.containedBefore() - } - val result = execute(testCase) - if (testCase.isRootTest()) { - containedRobolectricRunner.containedAfter() - } - result + if (testCase.isRootTest()) { + containedRobolectricRunner.containedBefore() + } + val result = execute(testCase) + if (testCase.isRootTest()) { + containedRobolectricRunner.containedAfter() } + return result } }