6

Testing Hybrid Jetpack Compose Apps

 3 years ago
source link: https://adavis.info/2021/09/testing-hybrid-jetpack-compose-apps.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Testing Hybrid Jetpack Compose Apps

September 6, 2021 by Annyce Davis 0 Comments

carbon.png?resize=489%2C366

We’re developing a hybrid Jetpack Compose application. It’s comprised of one Activity and several Fragments. Each fragment includes a ComposeView directly as the entire screen is built with Compose. But how do we write tests for this?

The problem

Using a hybrid Compose architecture reduced the learning curve for the team. However, this meant we had to make a tradeoff. Instead of using the Navigation component with Compose support, we’re using a standard nav_graph.xml file with destinations defined in XML.

<?xml version="1.0" encoding="utf-8"?> <navigation xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" android:id="@+id/nav_graph" app:startDestination="@id/homeFragment">

<fragment android:id="@+id/homeFragment" android:name="com.meetup.organizer.home.HomeFragment" android:label="HomeFragment"> <action android:id="@+id/action_homeFragment_to_nonOrganizerFragment" app:destination="@id/nonOrganizerFragment" /> </fragment> <fragment android:id="@+id/nonOrganizerFragment" android:name="com.meetup.organizer.auth.NonOrganizerFragment" android:label="NonOrganizerFragment" /> </navigation>

In order to test the various composables housed inside of fragment classes, we’d need to effectively navigate to the desired fragment.

The solution

AndroidComposeTestRule. The AndroidComposeTestRule was specifically designed for the use-case where compose content is hosted by an Activity. This test rule allows you to gain access to the activity and thus its navController. Which is exactly what we need to successfully navigate to the various fragments.

Getting set up

We’ll start by pulling in the appropriate dependencies. In my application, I’m using Gradle’s Version Catalogs. Version Catalogs allow you to easily add dependencies with autocompletion support in Android Studio.

First, add the following dependencies to the libs.versions.toml file:

compose-ui-test-junit = { module = "androidx.compose.ui:ui-test-junit4", version.ref = "compose" }
compose-ui-test-manifest = { module = "androidx.compose.ui:ui-test-manifest", version.ref = "compose" }
espresso-intents = { module = "androidx.test.espresso:espresso-intents", version.ref = "espresso" }

I’ve included the Espresso Intents dependency as well. This will allow us to validate the click listeners that start new activities.

Next, in your module’s build.gradle.kts file you will need to reference those dependencies:

// Espresso intents
androidTestImplementation(libs.espresso.intents)
// Compose test rules and transitive dependencies
androidTestImplementation(libs.compose.ui.test.junit)
// Needed for createComposeRule
debugImplementation(libs.compose.ui.test.manifest)

👩🏽‍💻 Don’t forget to add the testInstrumentationRunner

defaultConfig {
     ...
    testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
}

Due to a configuration issue, I also needed to add the following packaging options to get the tests to build:

packagingOptions {
    resources.excludes += "META-INF/AL2.0"
    resources.excludes += "META-INF/LGPL2.1"
}

Now we have just what we need to start writing some tests.

Adding test tags

Unlike View-based UIs, you don’t just reference a composable by id. To interact with the UI hierarchy you’ll need to use Semantics. In this context, semantics is how you mark a piece of Compose UI for later access.

The most straightforward approach I found is to create string constants and then add them to the desired composable as a Modifier.testTag(). Here are a few examples.

const val NON_ORGANIZER_MAIN_IMAGE = "non_organizer_main_image"
const val NON_ORGANIZER_MAIN_TEXT = "non_organizer_main_text"
const val NON_ORGANIZER_CONTACT_US = "non_organizer_contact_us"

Once the tags are defined, I add them to the composables using the modifier property.

Image(
    painter = painterResource(id = R.drawable.bicycle),
    modifier = Modifier
        .fillMaxWidth()
        .testTag(NON_ORGANIZER_MAIN_IMAGE),
)
TextButton(
    onClick = onContactUsClick,
    modifier = Modifier.testTag(NON_ORGANIZER_CONTACT_US)
)

Now we have a way to access these components in our tests.

Writing tests

We’ll start by navigating to our desired fragment, the NonOrganizerFragment. This is where the AndroidComposeTestRule comes into play. Before each test, we use the test rule to gain access to the activity’s navController and then navigate to the fragment.

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

@Before
fun goToNonOrganizerFragment() {
    composeTestRule.activityRule.scenario.onActivity {
        findNavController(it, R.id.nav_host_fragment)
            .navigate(R.id.nonOrganizerFragment)
    }
}

Now let’s write our first test. We want to confirm that the static screen elements are displayed. We’ll use our test tags that we defined previously to do that.

composeTestRule.onNodeWithTag(TAG).assertIsDisplayed()

The composeTestRule has a function onNodeWithTag that allows you to find a composable with a particular test tag. If the composable is found you can make various assertions. For this simple case, we only want to ensure that the element is displayed.

Putting it all together, I created a list of the test tags I was interested in. Then used the onEach statement to test that they were displayed.

@Test
fun shouldDisplayStaticContent() {
    listOf(
        NON_ORGANIZER_MAIN_IMAGE,
        NON_ORGANIZER_MAIN_TEXT,
        NON_ORGANIZER_CONTACT_US,
        NON_ORGANIZER_MEMBER_APP
    ).onEach {
        composeTestRule.onNodeWithTag(it)
            .assertIsDisplayed()
    }
}

You can view the full test class here where I’ve also included an example of using Espresso Intents.

Resources

Here are some useful resources to help you with your Compose UI testing:

Thanks for reading!


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK