diff --git a/sample-compose/app/build.gradle.kts b/sample-compose/app/build.gradle.kts index 0158a8b8c..19fbc79b2 100644 --- a/sample-compose/app/build.gradle.kts +++ b/sample-compose/app/build.gradle.kts @@ -104,6 +104,10 @@ android { } testOptions { + unitTests { + // Robolectric resource processing/loading https://github.com/robolectric/robolectric/pull/4736 + isIncludeAndroidResources = true + } unitTests.all { if (it.name != "testStagingDebugUnitTest") { it.extensions.configure(kotlinx.kover.api.KoverTaskExtension::class) { @@ -159,6 +163,13 @@ dependencies { testImplementation("io.mockk:mockk:${Versions.TEST_MOCKK_VERSION}") testImplementation("app.cash.turbine:turbine:${Versions.TEST_TURBINE}") + // Instrument test with Robolectric + // Need to have BOM for testImplementation https://github.com/gradle/gradle/issues/23347 + testImplementation(platform("androidx.compose:compose-bom:${Versions.COMPOSE_BOM_VERSION}")) + testImplementation("androidx.compose.ui:ui-test-junit4") + testImplementation("androidx.test:rules:${Versions.TEST_RULES_VERSION}") + testImplementation("org.robolectric:robolectric:${Versions.TEST_ROBOLECTRIC_VERSION}") + // Instrument test // Need to have BOM for androidTestImplementation https://github.com/gradle/gradle/issues/23347 androidTestImplementation(platform("androidx.compose:compose-bom:${Versions.COMPOSE_BOM_VERSION}")) diff --git a/sample-compose/app/src/test/java/co/nimblehq/sample/compose/ui/screens/home/HomeScreenTest.kt b/sample-compose/app/src/test/java/co/nimblehq/sample/compose/ui/screens/home/HomeScreenTest.kt new file mode 100644 index 000000000..6a85edbe8 --- /dev/null +++ b/sample-compose/app/src/test/java/co/nimblehq/sample/compose/ui/screens/home/HomeScreenTest.kt @@ -0,0 +1,85 @@ +package co.nimblehq.sample.compose.ui.screens.home + +import androidx.activity.compose.setContent +import androidx.compose.ui.test.* +import androidx.compose.ui.test.junit4.ComposeContentTestRule +import androidx.compose.ui.test.junit4.createAndroidComposeRule +import androidx.test.rule.GrantPermissionRule +import co.nimblehq.sample.compose.domain.model.Model +import co.nimblehq.sample.compose.domain.usecase.UseCase +import co.nimblehq.sample.compose.test.CoroutineTestRule +import co.nimblehq.sample.compose.ui.AppDestination +import co.nimblehq.sample.compose.ui.screens.MainActivity +import co.nimblehq.sample.compose.ui.theme.ComposeTheme +import io.mockk.every +import io.mockk.mockk +import kotlinx.coroutines.flow.flowOf +import org.junit.* +import org.junit.Assert.assertEquals +import org.junit.runner.RunWith +import org.robolectric.RobolectricTestRunner + +@RunWith(RobolectricTestRunner::class) +class HomeScreenTest { + + private val coroutinesRule = CoroutineTestRule() + + @get:Rule + val composeRule = createAndroidComposeRule() + + /** + * More test samples with Runtime Permissions https://alexzh.com/ui-testing-of-android-runtime-permissions/ + */ + @get:Rule + val permissionRule: GrantPermissionRule = GrantPermissionRule.grant( + android.Manifest.permission.CAMERA + ) + + private val mockUseCase: UseCase = mockk() + + private lateinit var viewModel: HomeViewModel + private var expectedAppDestination: AppDestination? = null + + @Before + fun setup() { + every { mockUseCase() } returns flowOf( + listOf(Model(1), Model(2), Model(3)) + ) + + viewModel = HomeViewModel( + mockUseCase, + coroutinesRule.testDispatcherProvider + ) + } + + @Test + fun `when entering the Home screen, it shows UI correctly`() = initComposable { + onNodeWithText("Home").assertIsDisplayed() + } + + @Test + fun `when loading list item successfully, it shows the list item correctly`() = initComposable { + onNodeWithText("1").assertIsDisplayed() + onNodeWithText("2").assertIsDisplayed() + onNodeWithText("3").assertIsDisplayed() + } + + @Test + fun `when clicking on a list item, it navigates to Second screen`() = initComposable { + onNodeWithText("1").performClick() + + assertEquals(expectedAppDestination, AppDestination.Second) + } + + private fun initComposable(testBody: ComposeContentTestRule.() -> Unit) { + composeRule.activity.setContent { + ComposeTheme { + HomeScreen( + viewModel = viewModel, + navigator = { destination -> expectedAppDestination = destination } + ) + } + } + testBody(composeRule) + } +} diff --git a/sample-compose/app/src/test/resources/robolectric.properties b/sample-compose/app/src/test/resources/robolectric.properties new file mode 100644 index 000000000..ae44aeb65 --- /dev/null +++ b/sample-compose/app/src/test/resources/robolectric.properties @@ -0,0 +1,3 @@ +# Workaround for issue https://github.com/robolectric/robolectric/issues/6593 +instrumentedPackages=androidx.loader.content +sdk=30 diff --git a/sample-compose/buildSrc/src/main/java/Versions.kt b/sample-compose/buildSrc/src/main/java/Versions.kt index ab4243dfd..33b3c1885 100644 --- a/sample-compose/buildSrc/src/main/java/Versions.kt +++ b/sample-compose/buildSrc/src/main/java/Versions.kt @@ -44,6 +44,7 @@ object Versions { const val TEST_JUNIT_VERSION = "4.13.2" const val TEST_KOTEST_VERSION = "5.5.4" const val TEST_MOCKK_VERSION = "1.12.3" + const val TEST_ROBOLECTRIC_VERSION = "4.9.2" const val TEST_RULES_VERSION = "1.5.0" const val TEST_TURBINE = "0.12.1" }