androidandroid-jetpack-composeandroid-jetpack-compose-testing

Writing tests for Composable screen with repeated items in Jetpack Compose


I am currently working on a Composable screen in a Jetpack Compose-based Android application. The screen displays a list of products using card-style views. Each card is tagged with a product code for testing purposes.

I've encountered a scenario where I need to write tests for my Composable screen that includes repeated items. In this example, I am displaying a list of products(name, price), and each card has test tag as product code.

I've set up my test data with two products that share the same price, and I'm using the composeTestRule to render the screen. However, I'm unsure about the best approach to validate the behavior of these duplicate-priced products within my tests.

@Composable
fun ProductListScreen(products: List<Product>) {
    Column {
        products.forEach { product ->
            ProductCard(product)
        }
    }
}

// My test code
@Test
fun testTwoProductsSamePrice() {
    val products = listOf(
        Product("Product A", 10.0, "code1"),
        Product("Product B", 20.0, "code2"),
        Product("Product C", 10.0, "code3")
    )

    composeTestRule.setContent {
        ProductListScreen(products)
    }

     products.forEach { product ->
    // Find the card for the current product using its content description
    val cardTag = "product_card_${product.productCode}"
    val cardNode = onNodeWithTag(cardTag)
    cardNode.assertIsDisplayed()

    // Inside the card, assert the presence of various UI elements

    onNodeWithText(product.name).assertIsDisplayed()
    onNodeWithText("Price: ${product.price}").assertIsDisplayed()

}

My doubt is asserting with onNodeWithText for price may not be a good idea, as for any reason the second product's price is not displayed, this test still can pass. Can someone guide the right assertions here. I am looking for something like associating with the card here which has product card to identify each card uniquely.


Solution

  • You can combine matchers with and operator. Find a node which contains a product and price like below.

    // this will find a ProductCard that has a given product name text and price text
    onNode(
        hasAnyChild(hasText(product.name)) and hasAnyChild(hasText("Price: ${product.price}"))
    ).assertIsDisplayed()
    

    Your test code will be like this

    // My test code
    @Test
    fun testTwoProductsSamePrice() = with(composeTestRule) {
        val products = listOf(
            Product("Product A", 10.0, "code1"),
            Product("Product B", 20.0, "code2"),
            Product("Product C", 10.0, "code3")
        )
    
        setContent {
            ProductListScreen(products)
        }
    
        products.forEach { product ->
            // Find the card for the current product using its content description
            val cardTag = "product_card_${product.productCode}"
            val cardNode = onNodeWithTag(cardTag)
            cardNode.assertIsDisplayed()
    
            // Inside the card, assert the presence of various UI elements
            onNode(
                hasAnyChild(hasText(product.name)) and hasAnyChild(hasText("Price: ${product.price}"))
            ).assertIsDisplayed()
        }
    }