How to inject AndroidComposeTestRule into classes?

141 views Asked by At

I want to achieve something like this:

// NavigateToUserAccountTest.kt
@ExperimentalMaterialApi
@HiltAndroidTest
class NavigateTest {

    @EarlyEntryPoint
    @InstallIn(SingletonComponent::class)
    internal interface HiltTestEntryPoint {
        val rule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>
    }

    @get:Rule(order = 0)
    val hiltRule = HiltAndroidRule(this)

    @get:Rule(order = 1)
    val rule = createAndroidComposeRule<MainActivity>() = EarlyEntryPoints.get(ApplicationProvider.getApplicationContext(), HiltTestEntryPoint::class.java).rule

    @Inject
    lateinit var bottomNavigation : BottomNavigation 

    @Before
    fun init() {
        hiltRule.inject()
    }

    @Test
    fun userAccountTab_is_displayed() {
        BottomNavigation.userAccountTab.assertIsDisplayed()
    }

    @Test
    fun shoppingTab_is_displayed{
        BottomNavigation.shoppingTab.assertIsDisplayed()
    }
}

// BottomNavigation.kt
class BottomNavigation @Inject constructor(
) {
    private val rule: AndroidComposeTestRule<ActivityScenarioRule<MainActivity> = EarlyEntryPoints.get(ApplicationProvider.getApplicationContext(), HiltTestEntryPoint::class.java).rule
    val userAccountTab get() = rule.onNode(hasText(rule.activity.getString(R.string.nav_user_account)))
    val shoppingTab get() = rule.onNode(hasText(rule.activity.getString(R.string.nav_shopping)))
}

Basically I want to encapsulate SemanticsMatcher inside a class so that class contains the same SemanticsMatcher that relate to the same screen. For example, BottomNavigation has userAccountTab. So that I can reuse this class injected into different tests. My current solution is to manually instantiate BottomNavigation by passing rule as the contructor parameter. I want to avoid those boilerplate code by using Hilt injection.

I tried to create a module like this

@Module
@InstallIn(SingletonComponent::class)
class UITestModule {
    @Provides
    @Singleton
    fun provideMainActivityAndroidComposeRule() = createAndroidComposeRule<MainActivity>()
}

and change rule to

@Inject
@get:Rule
lateinit var rule:  AndroidComposeTestRule<ActivityScenarioRule<MainActivity>, MainActivity>

But this does not work because injection only happens when call hiltRule.inject().

Is it possible to inject rule into other classes?

Update:

So I am so close to make it working, by using EarlyEntryPoint. But on the second test, it gives an exception below.

java.lang.IllegalStateException: Only a single call to `runTest` can be performed during one test.
at kotlinx.coroutines.test.TestScopeImpl.enter(TestScope.kt:220)
at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTest(TestBuilders.kt:165)
at kotlinx.coroutines.test.TestBuildersKt.runTest(Unknown Source:1)
at kotlinx.coroutines.test.TestBuildersKt__TestBuildersKt.runTest$default(TestBuilders.kt:161)
at kotlinx.coroutines.test.TestBuildersKt.runTest$default(Unknown Source:1)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.withTestCoroutines(ComposeUiTest.android.kt:342)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.access$withTestCoroutines(ComposeUiTest.android.kt:218)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1$1.invoke(ComposeUiTest.android.kt:294)
at androidx.compose.ui.test.junit4.EspressoLink.withStrategy(EspressoLink.android.kt:66)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1$1.invoke(ComposeUiTest.android.kt:293)
at androidx.compose.ui.test.junit4.IdlingResourceRegistry.withRegistry(IdlingResourceRegistry.jvm.kt:157)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment$runTest$1.invoke(ComposeUiTest.android.kt:292)
at androidx.compose.ui.test.junit4.ComposeRootRegistry.withRegistry(ComposeRootRegistry.android.kt:146)
at androidx.compose.ui.test.AndroidComposeUiTestEnvironment.runTest(ComposeUiTest.android.kt:291)
at androidx.compose.ui.test.junit4.AndroidComposeTestRule$apply$1.evaluate(AndroidComposeTestRule.android.kt:147)
at dagger.hilt.android.internal.testing.MarkThatRulesRanRule$1.evaluate(MarkThatRulesRanRule.java:108)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.BlockJUnit4ClassRunner$1.evaluate(BlockJUnit4ClassRunner.java:100)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:366)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:103)
at org.junit.runners.BlockJUnit4ClassRunner.runChild(BlockJUnit4ClassRunner.java:63)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runners.Suite.runChild(Suite.java:128)
at org.junit.runners.Suite.runChild(Suite.java:27)
at org.junit.runners.ParentRunner$4.run(ParentRunner.java:331)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:79)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:329)
at org.junit.runners.ParentRunner.access$100(ParentRunner.java:66)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:293)
at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
at org.junit.runners.ParentRunner.run(ParentRunner.java:413)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at org.junit.runner.JUnitCore.run(JUnitCore.java:115)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:67)
at androidx.test.internal.runner.TestExecutor.execute(TestExecutor.java:58)
at androidx.test.runner.AndroidJUnitRunner.onStart(AndroidJUnitRunner.java:446)
at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:2278)

``

EarlyEntryPoints.get(ApplicationProvider.getApplicationContext(), HiltTestEntryPoint::class.java).rule

returns the same AndroidComposeTestRule in the test and in BottomNavigation. I don't what is wrong with that... I am stuck.

0

There are 0 answers