Essentials of Composable UI Testing: Understanding Basic Component Isolation
In the last few years, making Android apps has changed a lot. Now, developers use declarative UI frameworks more, and Jetpack Compose is the newest one. Declarative UI frameworks give programmers tools to change how the system displays the user interface.
User interfaces that are created in a declarative way can use these control structures to be more dynamic than the commonly used imperative frameworks for developing apps on Android and iOS.
Jetpack is more powerful and easier to test and debug. However, testing with Jetpack Compose can still be challenging, especially for those new to the framework.
That’s why I gonna talk about Compose Layout UI testing everything I know.
Why Compose UI Testing is so Important
Testing UIs or screens is used to verify the correct behavior of your Compose code, improving the quality of your app by catching errors early in the development process. UI testing can give you so many benefits just like this
- Clear and Concise
- Independent
- Ensure Correct Behavior
- Repeatable
- Precise
- Fast
- Continuous Integration
Testing in Compose UI
UI tests simulate user actions on your app’s user interface and assert a visual outcome.
On Android, we use the Compose testing framework for that (Espresso for XML)
Warning: UI Tests have a high risk of becoming flaky tests. Sometimes it might be failed, sometimes it will be passed and sometimes it will provide unreliable results. That’s why you must choose the correct view matches that match unique UI components.
There are 3 Types of UI testing
- Isolated UI Test → tests a single UI component in isolation ( eg: stateless or reusable component for EmailField or PhoneNumberField).
- Integrated UI Test → tests how UI component interacts with other classes (such as ViewModel)
- End-to-End Test → tests complete user interaction flows, typically across multiple screens
Note: When you are using the Composable Screen, keep distracting ViewModel as much as you can. If you are not doing just like this, You need to mock ViewModel or Fake Data to test this component. This is not good.
How to know what to test
When thinking about writing a test, ask yourself these 4 questions to get a feeling for whether you should write a test or not.
- How important is it for the essential features to work properly?
- What value does it bring to the business?
- How hard is this code to understand?
- How probable is it that the code will be altered in the future?
If you struggle to decide, write one.
Composable UI Setup
we need to add the following dependencies to the build.gradle
file of the module containing your UI tests:
Compose Test APIs:
// Test rules and transitive dependencies:
androidTestImplementation("androidx.compose.ui:ui-test-junit4:$compose_version")
// Needed for createAndroidComposeRule, but not createComposeRule:
debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")
Some people are doing Espresso testing but I lack of knowledge Espresso testing. 😅
Begin Your Journey
We have a simpleGreeting
composable function.
@Composable
fun Greeting(name: String, modifier: Modifier = Modifier) {
Text(
text = "Hello $name!",
modifier = modifier.testTag("GreetingText")
)
}
we want to test whether this text is showing or not on our screen.
Firstly you need to declare and create testRule.
@get:Rule
val composeTestRule = createAndroidTestRule(MainActivity::class.java)
// with test JUnit4 extension
@get: Rule
val composeTestRule = createComposeRule()
Then we will create a Test function. In this cases, please care about test naming Rule
Recommdend → “MethodName/behavior” + “Condition” + ExpectedOutcome”
@Test
fun testGreetings_withCorrectName_showCorrectly() {
val expectedValue = "Android"
with(composeTestRule) {
setContent { Greeting(name = expectedValue) }
onNode(hasTestTag("GreetingText"), useUnmergedTree = true)
.assertIsDisplayed()
}
}
Let me explain
composeTestRule
– a TestRule to test UI created with ComposeonNode
– a finderhasTestTag
– a MatcheruseUnmergedTree
– a parameter that controls the UI tree hierarchy asssertIsDisplayed
– an assertion
If you want to click some composable components, you can addperformClick
– an action
CreateTestRule
The createTestRule function locates a UI element based on its semantic attributes, such as test tags, content descriptions, or a custom property of a Composable. It can access the entire semantic tree of the UI present on the screen.
Finders
Finders search for the Composable that matches specific criteria and return a SemanticsNodeInteraction
containing the Composable and its children if any exist.
Some common finders include:
onNode
: Searches for a single Composable that matches. Throws an exception if more than one matching Composable is found.onNodeWithTag
: Searches for a single Composable with the test tag.onNodeWithText
: Searches for a single Composable with the specified text. A localized string can be retrieved usingandroidComposeTestRule.activity.getString(R.string.*)
.onAllNodes
: Looks for all nodes with matching and returns a non-iterableSemanticsNodeInteractionCollection
containing found Composables and their possible children.
Matchers
A matcher defines the criteria a finder uses to locate the Composable. For instance:
hasContentDescription
: Verifies that the Composable has the specified content description.hasTestTag
: Verifies that the Composable has the specified test tag.isRoot
: Verifies that it is the root Composable. etc …
There are also Hierarchical matchers and Selectors.
Hierarchical matchers → check the position of the Composable in the UI tree using methods like hasParent()
or hasAnyChild()
.
Selectors → can identify Composables around and filter them.
For example, given the following tree:
|- Root composable
|- ButtonOne
|- ButtonTwo
|- ButtonThree
Calling onSiblings()
on ButtonTwo
will return ButtonOne
and ButtonThree
Composables.
useUnmergedTree
Since the Compose layout flattens its UI tree, some UI elements may be combined into a single Composable. For instance, two texts can be merged into a single Text Composable, leading to the potential loss of semantics. To inspect an intact UI tree, useUnmergedTree
should be set to true.
Assertions
Assertions verify that the Composable meets specific conditions. Common assertions include:
assertExists
assertIsEnabled
assertTextEquals
assertContentDescription
etc…
By using the generic assert()
, you can supply your matcher and verify that it is satisfied for this node.
Actions
Actions simulate user events on Composables, such as:
performClick
performScroll
performTextInput
etc …
They also support various types of gestures.
If you want to know more about more complex UI Testing, you can look
from the Android.
I will cover in next topics because Composable UI testing is so large and wide area to cover.
Please start your composable testing and have a fun part of using this. When you are struggling to write a test, you can freely contact to me. I am happy to serve it
Thank you for reading 😃