Jetpack Compose

Get String resources in Jetpack Compose Tests

Many developers use string resources for development, and we can use similar resources for testing Jetpack Compose code with UI tests.
Alex Zhukovich 3 min read
Get String resources in Jetpack Compose Tests
Table of Contents

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 the AndroidComposeTestRule<ActivityScenarioRule<A>, A> instance. This option is useful when we want to use a custom Activity 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, the ComposeTestRule instance wouldn't provide access to the Activity and you wouldn't be able to use the setContent { ... } 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 the Activity instance.
  • The createAndroidComposeRule(): AndroidComposeTestRule<ActivityScenarioRule<A>, A> is helpful when we want to use custom Activityor we want to have access to application resources.
  • The createEmptyComposeRule(): ComposeTestRule is useful when we want to launch an Activity 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.


Mobile development with Alex

A blog about Android development & testing, Best Practices, Tips and Tricks

Share
More from Mobile development with Alex
Jetpack Compose: Divider
Jetpack Compose

Jetpack Compose: Divider

This article covers using and customizing the “Dividers” components from the "Material 2" and "Material 3" libraries in the Jetpack Compose. In addition to that, we will explore the difference between implementation of the Divider, HorizontalDivider and VerticalDivider.
Alex Zhukovich 4 min read

Great! You’ve successfully signed up.

Welcome back! You've successfully signed in.

You've successfully subscribed to Mobile development with Alex.

Success! Check your email for magic link to sign-in.

Success! Your billing info has been updated.

Your billing was not updated.