Introduction
Many Android applications use string resources for labels instead of hard-coded string values because it helps to use translatable values. Usually, the language of the application installed on the device depends on the system language. This is the reason why test devices often have a default language similar to the default language of the application.
Suppose you have hardcoded string values, like Login
in your test case, and your device's language is not English. Your test case will fail because a translated value of the Login
label will be different in another language.
In the case of using string resources in tests, you will use the translated value instead of the hardcoded value. This is why many test cases use resources instead of a string.
When we look at many examples related to testing Jetpack Compose code, we can see that the createComposeRule()
function is used to test composable code in UI tests. Unfortunately, we don't have access to string resources outside of the setContent { ... }
method.
In this article, we will explore:
- different test rules which allow us to test Jetpack Compose code
- how can we get access to string resources in test cases
Jetpack Compose rules
The androidx.compose.ui:ui-test-junit4
dependency provides access to a few "JUnit Test Rules" which help us test Jetpack Compose code.
- The
createComposeRule(): ComposeContentTestRule
is helpful when you want to verify composable function in isolation and don't need access to Activity and application resources. - The
createAndroidComposeRule
returns theAndroidComposeTestRule<ActivityScenarioRule<A>, A>
instance. This option is useful when we want to use a customActivity
or we want to have access to application resources. - The
createEmptyComposeRule: ComposeTestRule
is helpful when you don't want to test composable functions in isolation. Instead, we want to launch an activity that already includes a composable host inside. In this case, theComposeTestRule
instance wouldn't provide access to theActivity
and you wouldn't be able to use thesetContent { ... }
method to add the composable function to the current screen.
When we want to run any test case, we always run an Activity
instance before launching the fragment.
What happens when we want to test a composable function in isolation? When we add the androidx.compose.ui:ui-test-manifest
dependency the ComponentActivity
will be added to the merged AndroidManifest.xml
file. The ComponentActivity
will be launched first, and our composable function will be added later. We will see it if we look at the createComposeRule()
function.
fun createComposeRule(): ComposeContentTestRule =
createAndroidComposeRule<ComponentActivity>()
Get String value in test case
To test composable function in isolation and have access to String
resources, we can use the createAndroidComposeRule<ComponentActivity>
function. Internally, it creates similar activity as the createComposeRule
do, but we will have access to the ComponentActivity
instance and can use application resources.
To get a String
value from the string resource, use the following code:
composeTestRule.activity.getString(resId)
We can extract it to a separate SemanticsMatcher
:
fun <A: ComponentActivity> hasText(@StringRes resId: Int, activity: A) =
hasText(activity.getString(resId))
If you have access to composeTestRule
object in place where you defined SemanticsMatcher
matcher, you can simpliy it by removing activity
parameter:
fun hasText(@StringRes resId: Int) =
hasText(composeTestRule.activity.getString(resId))
Let's take create a test that verifies that the "Settings Screen" has the "Account" and "Frequently Asked Questions" sections which are non-clickable.
The full example:
@ExperimentalMaterial3Api
@RunWith(AndroidJUnit4::class)
class SettingsScreenTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<ComponentActivity>()
/**
* For the demo purpose, I will use both functions.
*/
fun hasText(@StringRes resId: Int) =
hasText(composeTestRule.activity.getString(resId))
fun <A: ComponentActivity> hasText(@StringRes resId: Int, activity: A) =
hasText(activity.getString(resId))
@Test
fun shouldDisplaySettingsScreenWithAccountAndFaqSections_whenScreenIsLaunched() {
composeTestRule.apply {
setContent {
SettingsScreen(
onProfile = {},
onDocs = {}
)
}
onNode(hasText(R.string.settingsScreen_accountSection_label))
.assertIsDisplayed()
.assertHasNoClickAction()
onNode(hasText(R.string.settingsScreen_faqSection_label, composeTestRule.activity))
.assertIsDisplayed()
.assertHasNoClickAction()
}
}
}
You can also use the createAndroidComposeRule
for the existing Activity
.
@RunWith(AndroidJUnit4::class)
class SettingsScreenTest {
@get:Rule
val composeTestRule = createAndroidComposeRule<HomeActivity>()
/**
* For the demo purpose, I will use both functions.
*/
fun hasText(@StringRes resId: Int) =
hasText(composeTestRule.activity.getString(resId))
fun <A: ComponentActivity> hasText(@StringRes resId: Int, activity: A) =
hasText(activity.getString(resId))
@Test
fun shouldDisplaySettingsScreenWithAccountAndFaqSections_whenScreenIsLaunched() {
composeTestRule.apply {
onNode(hasText(R.string.settingsScreen_accountSection_label))
.assertIsDisplayed()
.assertHasNoClickAction()
onNode(hasText(R.string.settingsScreen_faqSection_label, composeTestRule.activity))
.assertIsDisplayed()
.assertHasNoClickAction()
}
}
}
Summary
We can use different "Test rules" for testing Android applications.
- The
createComposeRule(): ComposeContentTestRule
is useful for testing composable function in isolation when you don't want to get access to theActivity
instance. - The
createAndroidComposeRule(): AndroidComposeTestRule<ActivityScenarioRule<A>, A>
is helpful when we want to use customActivity
or we want to have access to application resources. - The
createEmptyComposeRule(): ComposeTestRule
is useful when we want to launch anActivity
which already include composable host inside.
If we have access to the Activity
we can easily get the String
value from the string resource using the composeTestRule.activity.getString(resId)
function.
We can test composable functions in isolation and have access to activity if we will use the createAndroidComposeRule<ComponentActivity>()
function.