Android Jetpack compose how to test background color

Viewed 1293

I have a composable that set the background color and I would like to test that.

@Composable
fun MyComposableButton(
    enabledColor: Color,
    disableColor: Color,
    isEnabled: Boolean = true,
) {
    val buttonBackgroundColor = if (enabled) enabledColor else disableColor
    Button(
        ...
        enabled = enabled,
        colors = ButtonDefaults.textButtonColors(
            backgroundColor = buttonBackgroundColor
        )
    ) { ... }
}

I'm expecting to write a tests like: verifyEnabledBackgroundColor and verifyDisabledBakcgroundColor.

I can't find any assertion directly available on the compose testing, and when trying to create my own I find that the SemanticMatcther uses a SemanticNode, but the constructor is internal for the latest so that is no go.

I try to mock the Color but I couldn't and according to this answer high API level would be required, which is a no for my project.

How can I test setting the background color for a composable?

3 Answers

[UPDATE] Please read the other answer https://stackoverflow.com/a/72629280/4017501

After a lot of trials and errors, I found a way to do it. There is an extension captureImage that has the color space. With that, we can find the color name and make an equal assertion.

It has some limitations though: it is the surface underlying the node, so multiple nodes or gradients maybe won't work.

fun SemanticsNodeInteraction.assertBackgroundColor(expectedBackground: Color) {
    val capturedName = captureToImage().colorSpace.name
    assertEquals(expectedBackground.colorSpace.name, capturedName)
}

I made an extension for reusability, for example:

composeTestRule.setContent {
    ...
}

composeTestRule.onNodeWithText(someText).assertBackgroundColor(YourColor)

Beware, this might be working because in the testing we are making sure to pass our theme:

composeTestRule.setContent {
    OurSuperCoolTheme { //your compose }
}

To correct cutiko accepted answer :

Validating color of composable based on colorspace.name does not work since the returned value is just a name of the color space. In other words test will pass regardless of the actual color.

Actual solution:

If the purpose of the test is to distinguish if the actual color of the composable is correct, for instance in a case where the color of a composable changes dynamically we need to use method .readPixels which provide "ARGB values packed into an Int. "

example usage:

val array = IntArray(20)
composeTestRule.onNodeWithTag(TestTags.CONTENT_TEXT_FIELD_TAG).captureToImage()
            .readPixels(array, startY = 500, startX = 200, width = 5, height = 4)
array.forEach { it shouldNotBe Colors().Red.convert(ColorSpaces.Srgb).hashCode() }
array.forEach { it shouldBe Colors().Pink.convert(ColorSpaces.Srgb).hashCode() }

Not perfect, but allows me to know if an icon is tinted with the correct color. Based on previous answers.

fun SemanticsNodeInteraction.assertContainsColor(tint: Color): SemanticsNodeInteraction {
    val imageBitmap = captureToImage()
    val buffer = IntArray(imageBitmap.width * imageBitmap.height)
    imageBitmap.readPixels(buffer, 0, 0, imageBitmap.width - 1, imageBitmap.height - 1)
    val pixelColors = PixelMap(buffer, 0, 0, imageBitmap.width - 1, imageBitmap.height - 1)

    (0 until imageBitmap.width).forEach { x ->
        (0 until imageBitmap.height).forEach { y ->
            if (pixelColors[x, y] == tint) return this
        }
    }
    throw AssertionError("Assert failed: The component does not contain the color")
}
Related