Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Add intent extras in compose UI test

I want to UI test an Activity that uses Jetpack Compose. The docs provide some information on how to test such a screen with two variants:

 @get:Rule val composeTestRule = createComposeRule()

if I don't need the activity itself to run and want to test just my composables or

 @get:Rule val composeTestRule = createAndroidComposeRule<MyActivity>()

if I do need the activity.

In the second case, how can I pass an Intent with Extras to the activity?

I've tried:

@Before
fun setUp() {
    composeTestRule.activity.intent = Intent().apply {
        putExtra(
            "someKey",
            123
        )
    }
}

but the intent extras are still null in the activity.

like image 538
fweigl Avatar asked Mar 11 '26 12:03

fweigl


2 Answers

The issue with setting composeTestRule.activity.intent in setUp() is that the Activity is already created at that point and the Activity's OnCreate was already called. So your intent properties that you're setting in setUp() are being set but it's too late to be consumed in Activity.OnCreate.

Unfortunately Google doesn't create a helper method like they do with createAndroidComposeRule<MyActivity>(), yet. However it's possible to write a helper method to work around:

Option 1 (An intent per test)

How to use

class MyActivityTest {

    @get:Rule
    val composeRule = createEmptyComposeRule()

    @Test
    fun firstTimeLogIn() = composeRule.launch<MyActivity>(
        onBefore = {
            // Set up things before the intent
        },
        intentFactory = {
            Intent(it, MyActivity::class.java).apply {
                putExtra("someKey", 123)
            }
        },
        onAfterLaunched = {
            // Assertions on the view 
            onNodeWithText("Username").assertIsDisplayed()
        })
}

Kotlin Extension Helper Method

import android.app.Activity
import android.content.Context
import android.content.Intent
import androidx.compose.ui.test.junit4.ComposeTestRule
import androidx.compose.ui.test.junit4.createEmptyComposeRule
import androidx.test.core.app.ActivityScenario
import androidx.test.core.app.ApplicationProvider

/**
* Uses a [ComposeTestRule] created via [createEmptyComposeRule] that allows setup before the activity
* is launched via [onBefore]. Assertions on the view can be made in [onAfterLaunched].
*/
inline fun <reified A: Activity> ComposeTestRule.launch(
    onBefore: () -> Unit = {},
    intentFactory: (Context) -> Intent = { Intent(ApplicationProvider.getApplicationContext(), A::class.java) },
    onAfterLaunched: ComposeTestRule.() -> Unit
) {
    onBefore()

    val context = ApplicationProvider.getApplicationContext<Context>()

    ActivityScenario.launch<A>(intentFactory(context)).use {
        onAfterLaunched()
    }
}

Option 2 (Single intent for each test class)

How to use

@RunWith(AndroidJUnit4::class)
class MyActivityTest {

    @get:Rule
    val composeTestRule = createAndroidIntentComposeRule<MyActivity> {
        Intent(it, MyActivity::class.java).apply {
            putExtra("someKey", 123)
        }
    }

    @Test
    fun Test1() {

    }

}

Kotlin Extension Helper Method

import android.content.Context
import android.content.Intent
import androidx.activity.ComponentActivity
import androidx.compose.ui.test.junit4.AndroidComposeTestRule
import androidx.test.core.app.ApplicationProvider
import androidx.test.ext.junit.rules.ActivityScenarioRule

/**
* Factory method to provide Android specific implementation of createComposeRule, for a given
* activity class type A that needs to be launched via an intent.
*
* @param intentFactory A lambda that provides a Context that can used to create an intent. A intent needs to be returned.
*/
inline fun <A: ComponentActivity> createAndroidIntentComposeRule(intentFactory: (context: Context) -> Intent) : AndroidComposeTestRule<ActivityScenarioRule<A>, A> {
    val context = ApplicationProvider.getApplicationContext<Context>()
    val intent = intentFactory(context)

    return AndroidComposeTestRule(
        activityRule = ActivityScenarioRule(intent),
        activityProvider = { scenarioRule -> scenarioRule.getActivity() }
    )
}

/**
* Gets the activity from a scenarioRule.
*
* https://androidx.tech/artifacts/compose.ui/ui-test-junit4/1.0.0-alpha11-source/androidx/compose/ui/test/junit4/AndroidComposeTestRule.kt.html
*/
fun <A : ComponentActivity> ActivityScenarioRule<A>.getActivity(): A {
    var activity: A? = null

    scenario.onActivity { activity = it }

    return activity ?: throw IllegalStateException("Activity was not set in the ActivityScenarioRule!")
}
like image 155
ryanholden8 Avatar answered Mar 14 '26 12:03

ryanholden8


class ActivityTest {

    private lateinit var scenario: ActivityScenario<Activity>

    @get:Rule
    val composeAndroidRule = createEmptyComposeRule()


    @Before
    fun setUp() {

        scenario = ActivityScenario.launch(
            createActivityIntent(
                InstrumentationRegistry.getInstrumentation().targetContext,
            )
        )
    }

    private fun createActivityIntent(
        context: Context
    ): Intent {
        val intent = Intent(context, Activity::class.java)
        return intent
    }

}

hopefully, this code snippet would works for you

like image 45
Oscar Ivan Avatar answered Mar 14 '26 12:03

Oscar Ivan



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!