Testing APIs

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.