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.
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:
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()
})
}
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()
}
}
@RunWith(AndroidJUnit4::class)
class MyActivityTest {
@get:Rule
val composeTestRule = createAndroidIntentComposeRule<MyActivity> {
Intent(it, MyActivity::class.java).apply {
putExtra("someKey", 123)
}
}
@Test
fun Test1() {
}
}
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!")
}