There are three main ways to interact with UI elements:
- Finders let you select one or multiple elements (or nodes in the semantics tree) to make assertions or perform actions on them.
- Assertions are used to verify that the elements exist or have certain attributes.
- Actions inject simulated user events on the elements, such as clicks or other gestures.
Some of these APIs accept a SemanticsMatcher
to refer to one or more
nodes in the semantics tree.
Finders
You can use onNode
and onAllNodes
to select one or multiple nodes
respectively, but you can also use convenience finders for the most common
searches, such as onNodeWithText
, and
onNodeWithContentDescription
. You can browse the complete list in the
Compose Testing cheat sheet.
Select a single node
composeTestRule.onNode(<<SemanticsMatcher>>, useUnmergedTree = false): SemanticsNodeInteraction
// Example
composeTestRule
.onNode(hasText("Button")) // Equivalent to onNodeWithText("Button")
Select multiple nodes
composeTestRule
.onAllNodes(<<SemanticsMatcher>>): SemanticsNodeInteractionCollection
// Example
composeTestRule
.onAllNodes(hasText("Button")) // Equivalent to onAllNodesWithText("Button")
Unmerged tree
Some nodes merge the semantics information of their children. For example, a button with two text elements merges the text element labels:
MyButton {
Text("Hello")
Text("World")
}
From a test, use printToLog()
to show the semantics tree:
composeTestRule.onRoot().printToLog("TAG")
This code prints the following output:
Node #1 at (...)px
|-Node #2 at (...)px
Role = 'Button'
Text = '[Hello, World]'
Actions = [OnClick, GetTextLayoutResult]
MergeDescendants = 'true'
If you need to match a node of what would be the unmerged tree, you can set
useUnmergedTree
to true
:
composeTestRule.onRoot(useUnmergedTree = true).printToLog("TAG")
This code prints the following output:
Node #1 at (...)px
|-Node #2 at (...)px
OnClick = '...'
MergeDescendants = 'true'
|-Node #3 at (...)px
| Text = '[Hello]'
|-Node #5 at (83.0, 86.0, 191.0, 135.0)px
Text = '[World]'
The useUnmergedTree
parameter is available in all finders. For example, here
it's used in an onNodeWithText
finder.
composeTestRule
.onNodeWithText("World", useUnmergedTree = true).assertIsDisplayed()
Assertions
Check assertions by calling assert()
on the SemanticsNodeInteraction
returned by a finder with one or multiple matchers:
// Single matcher:
composeTestRule
.onNode(matcher)
.assert(hasText("Button")) // hasText is a SemanticsMatcher
// Multiple matchers can use and / or
composeTestRule
.onNode(matcher).assert(hasText("Button") or hasText("Button2"))
You can also use convenience functions for the most common assertions, such as
assertExists
, assertIsDisplayed
, and assertTextEquals
.
You can browse the complete list in the Compose Testing cheat sheet.
There are also functions to check assertions on a collection of nodes:
// Check number of matched nodes
composeTestRule
.onAllNodesWithContentDescription("Beatle").assertCountEquals(4)
// At least one matches
composeTestRule
.onAllNodesWithContentDescription("Beatle").assertAny(hasTestTag("Drummer"))
// All of them match
composeTestRule
.onAllNodesWithContentDescription("Beatle").assertAll(hasClickAction())
Actions
To inject an action on a node, call a perform…()
function:
composeTestRule.onNode(...).performClick()
Here are some examples of actions:
performClick(),
performSemanticsAction(key),
performKeyPress(keyEvent),
performGesture { swipeLeft() }
You can browse the complete list in the Compose Testing cheat sheet.
Matchers
A variety of matchers are available for testing your Compose code.
Hierarchical matchers
Hierarchical matchers let you go up or down the semantics tree and perform matching.
fun hasParent(matcher: SemanticsMatcher): SemanticsMatcher
fun hasAnySibling(matcher: SemanticsMatcher): SemanticsMatcher
fun hasAnyAncestor(matcher: SemanticsMatcher): SemanticsMatcher
fun hasAnyDescendant(matcher: SemanticsMatcher): SemanticsMatcher
Here are some examples of these matchers being used:
composeTestRule.onNode(hasParent(hasText("Button")))
.assertIsDisplayed()
Selectors
An alternative way to create tests is to use selectors which can make some tests more readable.
composeTestRule.onNode(hasTestTag("Players"))
.onChildren()
.filter(hasClickAction())
.assertCountEquals(4)
.onFirst()
.assert(hasText("John"))
You can browse the complete list in the Compose Testing cheat sheet.
Additional Resources
- Test apps on Android: The main Android testing landing page provides a broader view of testing fundamentals and techniques.
- Fundamentals of testing: Learn more about the core concepts behind testing an Android app.
- Local tests: You can run some tests locally, on your own workstation.
- Instrumented tests: It is good practice to also run instrumented tests. That is, tests that run directly on-device.
- Continuous integration: Continuous integration lets you integrate your tests into your deployment pipeline.
- Test different screen sizes: With some many devices available to users, you should test for different screen sizes.
- Espresso: While intended for View-based UIs, Espresso knowledge can still be helpful for some aspects of Compose testing.