11package co.nimblehq.sample.compose.ui.screens.main.home
22
33import androidx.activity.compose.setContent
4+ import androidx.compose.ui.platform.LocalContext
45import androidx.compose.ui.test.assertIsDisplayed
56import androidx.compose.ui.test.junit4.AndroidComposeTestRule
67import androidx.compose.ui.test.junit4.createAndroidComposeRule
8+ import androidx.compose.ui.test.longClick
79import androidx.compose.ui.test.onNodeWithText
810import androidx.compose.ui.test.performClick
11+ import androidx.compose.ui.test.performTouchInput
12+ import androidx.navigation.compose.ComposeNavigator
13+ import androidx.navigation.testing.TestNavHostController
914import androidx.test.ext.junit.rules.ActivityScenarioRule
1015import androidx.test.rule.GrantPermissionRule
1116import co.nimblehq.sample.compose.domain.usecases.GetModelsUseCase
1217import co.nimblehq.sample.compose.domain.usecases.IsFirstTimeLaunchPreferencesUseCase
1318import co.nimblehq.sample.compose.domain.usecases.UpdateFirstTimeLaunchPreferencesUseCase
1419import co.nimblehq.sample.compose.test.MockUtil
1520import co.nimblehq.sample.compose.test.TestDispatchersProvider
16- import co.nimblehq.sample.compose.ui.base.BaseDestination
21+ import co.nimblehq.sample.compose.ui.AppNavGraph
1722import co.nimblehq.sample.compose.ui.screens.MainActivity
1823import co.nimblehq.sample.compose.ui.screens.main.MainDestination
1924import co.nimblehq.sample.compose.ui.theme.ComposeTheme
25+ import dagger.hilt.android.testing.BindValue
26+ import dagger.hilt.android.testing.HiltAndroidRule
27+ import dagger.hilt.android.testing.HiltAndroidTest
2028import io.mockk.every
2129import io.mockk.mockk
2230import kotlinx.coroutines.flow.flowOf
23- import org.junit.Assert.assertEquals
2431import org.junit.Before
2532import org.junit.Rule
2633import org.junit.Test
2734
35+ @HiltAndroidTest
2836class HomeScreenTest {
37+ @get:Rule(order = 0 )
38+ var hiltRule = HiltAndroidRule (this )
2939
30- @get:Rule
40+ @get:Rule(order = 1 )
3141 val composeRule = createAndroidComposeRule<MainActivity >()
42+ private lateinit var navController: TestNavHostController
3243
3344 /* *
3445 * More test samples with Runtime Permissions https://alexzh.com/ui-testing-of-android-runtime-permissions/
@@ -38,24 +49,27 @@ class HomeScreenTest {
3849 android.Manifest .permission.CAMERA
3950 )
4051
41- private val mockGetModelsUseCase: GetModelsUseCase = mockk()
42- private val mockIsFirstTimeLaunchPreferencesUseCase: IsFirstTimeLaunchPreferencesUseCase = mockk()
43- private val mockUpdateFirstTimeLaunchPreferencesUseCase: UpdateFirstTimeLaunchPreferencesUseCase = mockk()
52+ private val mockGetModelsUseCase: GetModelsUseCase = mockk(relaxed = true )
53+ private val mockIsFirstTimeLaunchPreferencesUseCase: IsFirstTimeLaunchPreferencesUseCase = mockk(relaxed = true )
54+ private val mockUpdateFirstTimeLaunchPreferencesUseCase: UpdateFirstTimeLaunchPreferencesUseCase =
55+ mockk(relaxed = true )
4456
45- private lateinit var viewModel: HomeViewModel
46- private var expectedDestination: BaseDestination ? = null
57+ // Cannot mock viewModel with mockk here because it will throw ClassCastException
58+ // Ref: https://github.com/mockk/mockk/issues/321
59+ @BindValue
60+ val viewModel: HomeViewModel = HomeViewModel (
61+ mockGetModelsUseCase,
62+ mockIsFirstTimeLaunchPreferencesUseCase,
63+ mockUpdateFirstTimeLaunchPreferencesUseCase,
64+ TestDispatchersProvider
65+ )
4766
4867 @Before
4968 fun setUp () {
69+ hiltRule.inject()
70+
5071 every { mockGetModelsUseCase() } returns flowOf(MockUtil .models)
5172 every { mockIsFirstTimeLaunchPreferencesUseCase() } returns flowOf(false )
52-
53- viewModel = HomeViewModel (
54- mockGetModelsUseCase,
55- mockIsFirstTimeLaunchPreferencesUseCase,
56- mockUpdateFirstTimeLaunchPreferencesUseCase,
57- TestDispatchersProvider
58- )
5973 }
6074
6175 @Test
@@ -71,23 +85,52 @@ class HomeScreenTest {
7185 }
7286
7387 @Test
74- fun when_clicking_on_a_list_item__it_navigates_to_Second_screen () = initComposable {
88+ fun when_clicking_on_a_list_item__it_navigates_to_Second_screen () = initComposableNavigation {
7589 onNodeWithText(" 1" ).performClick()
7690
77- assertEquals(expectedDestination, MainDestination .Second )
91+ onNodeWithText(" Second" ).assertIsDisplayed()
92+
93+ navController.currentBackStackEntry?.destination?.hasRoute(MainDestination .Second .route, null )
94+ }
95+
96+ @Test
97+ fun when_long_clicking_on_a_list_item_and_click_edit__it_navigates_to_Third_screen () = initComposableNavigation {
98+ onNodeWithText(" 1" ).performTouchInput { longClick() }
99+
100+ onNodeWithText(" Edit" ).performClick()
101+
102+ onNodeWithText(" Third" ).assertIsDisplayed()
103+
104+ navController.currentBackStackEntry?.destination?.hasRoute(MainDestination .Third .route, null )
78105 }
79106
80107 private fun initComposable (
81108 testBody : AndroidComposeTestRule <ActivityScenarioRule <MainActivity >, MainActivity >.() -> Unit
82109 ) {
83110 composeRule.activity.setContent {
111+ navController = TestNavHostController (LocalContext .current)
112+ navController.navigatorProvider.addNavigator(ComposeNavigator ())
84113 ComposeTheme {
85114 HomeScreen (
86115 viewModel = viewModel,
87- navigator = { destination -> expectedDestination = destination }
116+ onNavigateToSecondScreen = {},
117+ onNavigateToThirdScreen = {},
88118 )
89119 }
90120 }
91121 testBody(composeRule)
92122 }
123+
124+ private fun initComposableNavigation (
125+ testBody : AndroidComposeTestRule <ActivityScenarioRule <MainActivity >, MainActivity >.() -> Unit
126+ ) {
127+ composeRule.activity.setContent {
128+ navController = TestNavHostController (LocalContext .current)
129+ navController.navigatorProvider.addNavigator(ComposeNavigator ())
130+ ComposeTheme {
131+ AppNavGraph (navController)
132+ }
133+ }
134+ testBody(composeRule)
135+ }
93136}
0 commit comments