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.