androidkotlinandroid-jetpack-composeui-testing

What's the best way to assertColor() on a Jetpack Compose Component


I have several components that take in a state, and set colors accordingly.

In order to test this, I need a good way to call something like assertColor() on a node.

My initial thought was to add semantics properties, however Google itself warns to avoid doing this if the properties are only to be used for testing, which will be the case with these components:

Warning: You should only use custom Semantics properties when it's hard to match a specific item, or you need to expose a certain state that would be hard to check using the given finders and matchers. In general, this pattern should be avoided if the custom properties are only used for testing, because they stay in and pollute the production app.

Further, the captureToImage() function can work, but is unreliable considering similar colors as they share the same ColorSpace.

I've seen posts referencing using a class-based approach, with holding that state logic inside the class, and returning the @Composable Unit from another function. However it is my preference to keep my code fully functional (function-based)

I have thought about making the helper state functions for the color public (currently private), however that involves exposing a function to the rest of the codebase that's really only to be used by this one composable.

It seems like there aren't any good solutions at the moment for testing colors in an easy way that doesn't pollute production code.

Does anyone have any advice, or has found a good balance with their unit tests for these properties?


Solution

  • What I ended up going with was a test util:

    fun SemanticsNodeInteractionsProvider.onNodeWithTint(@ColorRes value: Int) = onAllNodes(SemanticsMatcher.expectValue(SemanticsProperties.tint, value)).onFirst()
    

    With a custom SemanticsProperty helper:

    object SemanticsProperties {
        val tint = SemanticsPropertyKey<Int>("iconTint")
        var SemanticsPropertyReceiver.tint by tint
    }
    
    fun Modifier.testTint(@ColorRes tint: Int) = semantics { this.tint = tint }
    

    and then in the composables modifier, just setting like this:

    .testTint(lineTintRes)
    

    Then, in the test, it can be asserted like this:

    composeTestRule.onNodeWithTint(Theme.colors.background).assertIsDisplayed()
    

    It's probably not the best solution as it technically ships a bit of test code within each composable, but that seems to be the only way I've found that works.