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, como o app inteiro, uma única tela ou um elemento pequeno. Também é uma boa prática verificar se os elementos que podem ser compostos estão encapsulados corretamente e funcionam de forma independente, o que facilita e foca os testes de interface.

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 vai ser possível chamar o setContent em uma regra criada com createAndroidComposeRule() se ele já tiver sido chamado pela atividade.

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 de semântica 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 com a classe StateRestorationTester sem depender da recriação de atividades.

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, localidades, tamanhos de fonte, temas escuros e claros 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 atual de Configuration. Testar diferentes configurações diretamente em um teste é difícil, porque o teste precisa configurar propriedades no nível do dispositivo.

DeviceConfigurationOverride é uma API somente para testes que permite simular diferentes configuradores do dispositivo 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, envolva 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 de paisagem grande, mesmo que o dispositivo em que o teste está sendo executado não ofereça suporte direto a esse tamanho de janela:

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")
    }
}

Tratamento de falhas personalizado 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 declaraçã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 IU em caso de falha ou registrar gerenciadores personalizados para encaminhar a telemetria de falhas para 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 gerenciador recebe um objeto FailureContext que contém o erro original e uma lista de artefatos gerados pelos gerenciadores 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 pacote de testes inteiro

É possível ativar as capturas de artefatos de falha para um pacote de testes inteiro 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 do nível do conjunto:

  • 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 conceitos básicos e das técnicas de teste.
  • Conceitos básicos de 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 de instrumentação:também é uma boa prática executar testes de instrumentação. Ou seja, testes que são executados diretamente no dispositivo.
  • Integração contínua:com a integração contínua, é possível 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.