Padrões comuns

É possível testar seu app do Compose com abordagens e padrões bem estabelecidos.

Testar de forma isolada

A ComposeTestRule permite que você inicie uma atividade mostrando qualquer elemento combinável: o app inteiro, uma única tela ou um elemento pequeno. Também é uma boa prática verificar se os elementos combináveis estão encapsulados corretamente e se funcionam de forma independente, permitindo testes de UI mais fáceis e focados.

Isso não significa que você só pode criar testes de IU de unidade. Os testes voltados para partes maiores da IU também são muito importantes.

Acessar a atividade e os recursos depois de definir seu conteúdo

Muitas vezes, é necessário definir o conteúdo que está em teste usando composeTestRule.setContent e também acessar recursos de atividade, por exemplo, para declarar que um texto mostrado corresponde a um recurso de string. No entanto, não é possível chamar setContent em uma regra criada com createAndroidComposeRule() se a atividade já o tiver chamado.

Um padrão comum para fazer isso é criar uma AndroidComposeTestRule usando uma atividade vazia, como ComponentActivity.

class MyComposeTest {

    @get:Rule
    val composeTestRule = createAndroidComposeRule<ComponentActivity>()

    @Test
    fun myTest() {
        // Start the app
        composeTestRule.setContent {
            MyAppTheme {
                MainScreen(uiState = exampleUiState, /*...*/)
            }
        }
        val continueLabel = composeTestRule.activity.getString(R.string.next)
        composeTestRule.onNodeWithText(continueLabel).performClick()
    }
}

Observe que a ComponentActivity precisa ser adicionada ao arquivo AndroidManifest.xml do app. Para ativar, adicione esta dependência ao módulo:

debugImplementation("androidx.compose.ui:ui-test-manifest:$compose_version")

Propriedades de semântica personalizadas

É possível criar propriedades semânticas personalizadas para expor informações aos testes. Para fazer isso, defina um novo SemanticsPropertyKey e disponibilize-o usando o SemanticsPropertyReceiver.

// Creates a semantics property of type Long.
val PickedDateKey = SemanticsPropertyKey<Long>("PickedDate")
var SemanticsPropertyReceiver.pickedDate by PickedDateKey

Agora use essa propriedade no modificador semantics:

val datePickerValue by remember { mutableStateOf(0L) }
MyCustomDatePicker(
    modifier = Modifier.semantics { pickedDate = datePickerValue }
)

Após os testes, use SemanticsMatcher.expectValue para declarar o valor da propriedade:

composeTestRule
    .onNode(SemanticsMatcher.expectValue(PickedDateKey, 1445378400)) // 2015-10-21
    .assertExists()

Verificar a restauração do estado

Verifique se o estado dos elementos do Compose é restaurado corretamente quando a atividade ou o processo são recriados. Faça essas verificações sem depender da recriação de atividades com a classe StateRestorationTester.

Essa classe permite simular a recriação de um elemento combinável. Ela é especialmente útil para verificar a implementação de rememberSaveable.


class MyStateRestorationTests {

    @get:Rule
    val composeTestRule = createComposeRule()

    @Test
    fun onRecreation_stateIsRestored() {
        val restorationTester = StateRestorationTester(composeTestRule)

        restorationTester.setContent { MainScreen() }

        // TODO: Run actions that modify the state

        // Trigger a recreation
        restorationTester.emulateSavedInstanceStateRestore()

        // TODO: Verify that state has been correctly restored.
    }
}

Testar diferentes configurações de dispositivo

Os apps Android precisam se adaptar a muitas condições variáveis: tamanhos de janela, locais, tamanhos de fonte, temas escuro e claro e muito mais. A maioria dessas condições é derivada de valores no nível do dispositivo controlados pelo usuário e expostos com a instância Configuration atual. Testar diferentes configurações diretamente em um teste é difícil, já que o teste precisa configurar propriedades no nível do dispositivo.

DeviceConfigurationOverride é uma API somente para testes que permite simular diferentes configurações de dispositivos de maneira localizada para o conteúdo @Composable em teste.

O objeto complementar de DeviceConfigurationOverride tem as seguintes funções de extensão, que substituem as propriedades de configuração no nível do dispositivo:

Para aplicar uma substituição específica, encapsule o conteúdo em teste em uma chamada para a função de nível superior DeviceConfigurationOverride(), transmitindo a substituição a ser aplicada como um parâmetro.

Por exemplo, o código a seguir aplica a substituição DeviceConfigurationOverride.ForcedSize() para mudar a densidade localmente, forçando o elemento combinável MyScreen a ser renderizado em uma janela horizontal grande, mesmo que o dispositivo em que o teste está sendo executado não ofereça suporte a esse tamanho de janela diretamente:

composeTestRule.setContent {
    DeviceConfigurationOverride(
        DeviceConfigurationOverride.ForcedSize(DpSize(1280.dp, 800.dp))
    ) {
        MyScreen() // Will be rendered in the space for 1280dp by 800dp without clipping.
    }
}

Para aplicar várias substituições juntas, use DeviceConfigurationOverride.then():

composeTestRule.setContent {
    DeviceConfigurationOverride(
        DeviceConfigurationOverride.FontScale(1.5f) then
            DeviceConfigurationOverride.FontWeightAdjustment(200)
    ) {
        Text(text = "text with increased scale and weight")
    }
}

Processamento de falhas personalizadas em testes

A depuração de testes de interface geralmente exige a inspeção do estado da tela e da hierarquia de composição quando uma asserção falha.

O Compose oferece um pipeline nativo de tratamento de falhas para simplificar os diagnósticos. É possível capturar automaticamente capturas de tela e hierarquias de interface em caso de falha ou registrar manipuladores personalizados para encaminhar a telemetria de falhas a ferramentas externas.

Configurar o tratamento de falhas

Para configurar o tratamento de falhas, transmita um objeto TestFailurePolicy para ComposeUiTestConfig.

Capturar artefatos de falha integrados

Para capturar capturas de tela e hierarquias de interface em caso de falha no teste, defina as propriedades screenshotCaptureMode e uiHierarchyCaptureMode em TestFailurePolicy.

class MyComposeTest {
    private val customConfig = ComposeUiTestConfig(
        failurePolicy = TestFailurePolicy(
            screenshotCaptureMode = TestFailurePolicy.CaptureMode.Enabled,
            uiHierarchyCaptureMode = TestFailurePolicy.CaptureMode.Enabled
        )
    )

    @Test
    fun myFirstTest() = runComposeUiTest(config = customConfig) {
        // ...
    }
}

Usar um gerenciador de falhas personalizado

Para definir um comportamento personalizado para falhas de teste, adicione um TestFailureHandler à lista failureHandlers. Cada manipulador recebe um objeto FailureContext que contém o erro original e uma lista de artefatos gerados pelos manipuladores de pipeline anteriores.

class MyComposeTestWithHandler {
    private val customConfig = ComposeUiTestConfig(
        failurePolicy = TestFailurePolicy(
            screenshotCaptureMode = TestFailurePolicy.CaptureMode.Enabled,
            failureHandlers = listOf(
                TestFailureHandler { context ->
                    val screenshot = context.artifacts.firstOrNull {
                        it.type == FailureArtifact.Type.Screenshot
                    }
                    // ...
                }
            )
        )
    )

    @get:Rule
    val rule = createComposeRule(config = customConfig)

    // ...
}

Configurar capturas para um conjunto de testes inteiro

É possível ativar capturas de artefatos de falha para todo um conjunto de testes transmitindo argumentos do executor de instrumentação no arquivo build.gradle do módulo. Isso elimina a necessidade de atualizar arquivos de teste individuais.

// build.gradle.kts
android {
    defaultConfig {
        // ...
        testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isUiHierarchyCaptureEnabled"] = "true"
        testInstrumentationRunnerArguments["androidx.compose.ui.test.failure.isScreenshotCaptureEnabled"] = "true"
    }
}

Precedência de configuração

O framework avalia os argumentos do executor local TestFailurePolicy e da suíte:

  • Se você definir explicitamente um modo como CaptureMode.Enabled/Disabled no teste failurePolicy, isso vai substituir o argumento do executor.
  • Se você definir um modo como CaptureMode.Unspecified ou deixar failurePolicy como null, o framework vai usar o argumento do executor.

Outros recursos

  • Testar apps no Android: a página de destino principal de testes do Android oferece uma visão mais ampla dos princípios básicos e das técnicas de teste.
  • Fundamentos dos testes:saiba mais sobre os conceitos básicos por trás do teste de um app Android.
  • Testes locais:é possível executar alguns testes localmente, na sua própria estação de trabalho.
  • Testes instrumentados:é uma boa prática executar também testes instrumentados. Ou seja, testes executados diretamente no dispositivo.
  • Integração contínua:permite integrar seus testes ao pipeline de implantação.
  • Teste diferentes tamanhos de tela:com tantos dispositivos disponíveis para os usuários, é importante testar diferentes tamanhos de tela.
  • Espresso: embora seja destinado a interfaces baseadas em visualizações, o conhecimento do Espresso ainda pode ser útil para alguns aspectos dos testes do Compose.