Guia de teste do Hilt

Um dos benefícios de usar estruturas de injeção de dependência como o Hilt é que ele facilita o teste do código.

Testes de unidade

O Hilt não é necessário para testes de unidade. Ao testar uma classe que usa a injeção de construtor, você não precisa usar o Hilt para instanciar essa classe. Em vez disso, você pode chamar um construtor de classe diretamente passando dependências falsas ou simuladas, assim como faria se o construtor não fosse anotado:

@ActivityScoped
class AnalyticsAdapter @Inject constructor(
  private val service: AnalyticsService
) { ... }

class AnalyticsAdapterTest {

  @Test
  fun `Happy path`() {
    // You don't need Hilt to create an instance of AnalyticsAdapter.
    // You can pass a fake or mock AnalyticsService.
    val adapter = AnalyticsAdapter(fakeAnalyticsService)
    assertEquals(...)
  }
}

O mesmo se aplica às classes ViewModel obtidas chamando hiltViewModel() nos elementos combináveis. Em testes de unidade, crie o ViewModel diretamente com simulações. Para saber como o estado flui de uma ViewModel para elementos combináveis, consulte Estado e Jetpack Compose e Onde elevar o estado.

Testes de ponta a ponta

Para testes de integração, o Hilt injeta dependências como faria no código de produção. O teste com o Hilt não requer manutenção porque ele gera automaticamente um novo conjunto de componentes para cada teste.

Adicionar dependências de teste

Para usar o Hilt nos testes, inclua a dependência hilt-android-testing no seu projeto:

dependencies {
    // For Robolectric tests.
    testImplementation("com.google.dagger:hilt-android-testing:2.57.1")
    kspTest("com.google.dagger:hilt-android-compiler:2.57.1")

    // For instrumented tests.
    androidTestImplementation("com.google.dagger:hilt-android-testing:2.57.1")
    kspAndroidTest("com.google.dagger:hilt-android-compiler:2.57.1")

    // Compose UI test rule.
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")
    debugImplementation("androidx.compose.ui:ui-test-manifest")

}

Configuração de teste da IU

Anote qualquer teste de IU que use o Hilt com @HiltAndroidTest. Essa anotação é responsável por gerar os componentes do Hilt para cada teste.

Além disso, é necessário adicionar o HiltAndroidRule à classe de teste. Ele gerencia o estado dos componentes e é usado para realizar a injeção no teste:

@HiltAndroidTest
class SettingsScreenTest {

    @get:Rule(order = 0)
    val hiltRule = HiltAndroidRule(this)

    @get:Rule(order = 1)
    val composeRule = createAndroidComposeRule<HiltTestActivity>()

    // Compose UI tests here.
}

Em seguida, seu teste precisa saber sobre a classe Application que o Hilt gera automaticamente para você.

Para permitir que o Hilt injete dependências, crie uma atividade vazia chamada HiltTestActivity no conjunto de origem androidTest e anote-a com @AndroidEntryPoint. Em seguida, createAndroidComposeRule usa essa atividade como o host do seu conteúdo combinável.

Testar aplicativo

Execute testes instrumentados que usam o Hilt em um objeto Application com suporte. A biblioteca fornece o HiltTestApplication para uso em testes. Se os testes precisarem de um aplicativo base diferente, consulte Aplicativo personalizado para testes.

Execute seu aplicativo de testes nos testes de instrumentação ou testes do Robolectric. As instruções a seguir não são específicas para o Hilt, mas são diretrizes gerais sobre como especificar um aplicativo personalizado para execução em testes.

Definir o aplicativo de teste em testes de instrumentação

Para usar o aplicativo de teste do Hilt em testes de instrumentação, configure um novo executor de teste. Isso faz com que o Hilt funcione para todos os testes de instrumentação no seu projeto. Siga as etapas abaixo:

  1. Crie uma classe personalizada que estenda AndroidJUnitRunner na pasta androidTest.
  2. Substitua a função newApplication e transmita o nome do aplicativo de teste Hilt gerado.
// A custom runner to set up the instrumented application class for tests.
class CustomTestRunner : AndroidJUnitRunner() {

    override fun newApplication(cl: ClassLoader?, name: String?, context: Context?): Application {
        return super.newApplication(cl, HiltTestApplication::class.java.name, context)
    }
}

Em seguida, configure esse executor de testes no arquivo Gradle, conforme descrito no guia de teste de unidade instrumentado. Use o caminho de classe completo:

android {
    defaultConfig {
        // Replace com.example.android.dagger with your class path.
        testInstrumentationRunner = "com.example.android.dagger.CustomTestRunner"
    }
}
Definir o aplicativo de teste em testes do Robolectric

Se você usa o Roboletric para testar sua camada de IU, pode especificar qual aplicativo usar no arquivo robolectric.properties:

application = dagger.hilt.android.testing.HiltTestApplication

Como alternativa, é possível configurar o aplicativo em cada teste individualmente usando a anotação @Config do Robolectric:

@HiltAndroidTest
@Config(application = HiltTestApplication::class)
class SettingsScreenTest {

  @get:Rule
  var hiltRule = HiltAndroidRule(this)

  // Robolectric tests here.
}

Recursos de teste

Quando o Hilt estiver pronto para ser usado nos testes, você poderá usar vários recursos para personalizar esse processo.

Injetar tipos em testes

Para injetar tipos em um teste, use @Inject para injeção de campo. Para instruir o Hilt a preencher os campos @Inject, chame hiltRule.inject().

Veja um exemplo de teste de instrumentação:

@HiltAndroidTest
class SettingsScreenTest {

    @get:Rule(order = 0)
    val hiltRule = HiltAndroidRule(this)

    @get:Rule(order = 1)
    val composeRule = createAndroidComposeRule<HiltTestActivity>()

    @Inject
    lateinit var analyticsAdapter: AnalyticsAdapter

    @Before
    fun init() {
        hiltRule.inject()
    }

    @Test
    fun settingsScreen_showsTitle() {
        composeRule.setContent {
            SettingsScreen()
        }
        composeRule.onNodeWithText("Settings").assertIsDisplayed()
        // analyticsRepository is available here.
    }
}

Substituir uma vinculação

Se você precisar injetar uma instância falsa ou simulada de uma dependência, será necessário instruir o Hilt a não usar a vinculação do código de produção, mas sim uma diferente. Para substituir uma vinculação, é necessário substituir o módulo que contém a vinculação por um módulo de teste que contenha as vinculações que você quer usar no teste.

Por exemplo, suponha que seu código de produção declare uma vinculação para AnalyticsService da seguinte maneira:

@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

Para substituir a vinculação AnalyticsService nos testes, crie um novo módulo Hilt na pasta test ou androidTest com a dependência falsa e inclua uma anotação @TestInstallIn. Todos os testes nessa pasta vão ser injetados com a dependência falsa.

@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [AnalyticsModule::class]
)
abstract class FakeAnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    fakeAnalyticsService: FakeAnalyticsService
  ): AnalyticsService
}

Como os elementos combináveis geralmente consomem essas dependências indiretamente por um ViewModel obtido com hiltViewModel(), basta substituir a vinculação no Hilt. O elemento combinável em teste detecta automaticamente o falso.

Substituir uma vinculação em um único teste

Para substituir uma vinculação em um único teste em vez de todos os testes, desinstale um módulo do Hilt de um teste usando a anotação @UninstallModules e crie um novo dentro do teste.

Seguindo o exemplo AnalyticsService da versão anterior, comece pedindo ao Hilt para ignorar o módulo de produção usando a anotação @UninstallModules na classe de teste:

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsScreenTest { ... }

Em seguida, substitua a vinculação. Crie um novo módulo na classe de teste que defina a vinculação:

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsScreenTest {

  @Module
  @InstallIn(SingletonComponent::class)
  abstract class TestModule {

    @Singleton
    @Binds
    abstract fun bindAnalyticsService(
      fakeAnalyticsService: FakeAnalyticsService
    ): AnalyticsService
  }

  // ...
}

Isso substitui apenas a vinculação de uma única classe de teste. Para substituir a vinculação de todas as classes de teste, use a anotação @TestInstallIn da seção acima. Como alternativa, você pode colocar a vinculação de teste no módulo test para testes do Robolectric ou no módulo androidTest para testes de instrumentação. Recomendamos usar @TestInstallIn sempre que possível.

Vincular novos valores

Use a anotação @BindValue para vincular facilmente campos do seu teste ao gráfico de dependência do Hilt. Anote um campo com @BindValue e ele será vinculado ao tipo de campo declarado com qualquer qualificador presente nesse campo.

No exemplo AnalyticsService, você pode substituir um AnalyticsService por um falso usando @BindValue:

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsScreenTest {

  @BindValue @JvmField
  val analyticsService: AnalyticsService = FakeAnalyticsService()

  ...
}

Isso simplifica a substituição de uma vinculação e a referência no teste, permitindo que você faça as duas coisas ao mesmo tempo.

O @BindValue funciona com qualificador e outras anotações de teste. Por exemplo, se você usa bibliotecas de teste, como Mockito, pode usá-las em um teste do Robolectric da seguinte maneira:

...
class SettingsScreenTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

Se você precisar adicionar uma multivinculação, use as anotações @BindValueIntoSet e @BindValueIntoMap no lugar de @BindValue. @BindValueIntoMap requer que você também anote o campo com uma anotação de chave de mapa.

Casos especiais

O Hilt também oferece recursos compatíveis com casos de uso não padrão.

Aplicativo personalizado para testes

Se não for possível usar HiltTestApplication porque seu aplicativo de teste precisa estender outro app, adicione a anotação @CustomTestApplication a uma nova classe ou interface e transmita o valor da classe de base que será estendida pelo Hilt gerado.

Com o Hilt, o @CustomTestApplication vai gerar uma classe Application pronta para testes que estende o aplicativo transmitido como parâmetro.

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

No exemplo, o Hilt gera um Application chamado HiltTestApplication_Application que estende a classe BaseApplication. Em geral, o nome do aplicativo gerado é o nome da classe anotada acrescida de _Application. Defina o aplicativo de teste do Hilt gerado para ser executado nos seus testes de instrumentação ou testes do Robolectric, conforme descrito em Aplicativo de teste.

Vários objetos TestRule no teste de instrumentação

Os testes de interface do Compose já combinam HiltAndroidRule com uma regra de teste do Compose, como createAndroidComposeRule. Se você tiver outros objetos TestRule, verifique se HiltAndroidRule é executado primeiro. Declare a ordem de execução com o atributo order em @Rule:

@HiltAndroidTest
class SettingsScreenTest {

  @get:Rule(order = 0)
  var hiltRule = HiltAndroidRule(this)

  @get:Rule(order = 1)
  val composeRule = createAndroidComposeRule<HiltTestActivity>()

  @get:Rule(order = 2)
  val otherRule = SomeOtherRule()

  // UI tests here.
}

Como alternativa, é possível incluir as regras em RuleChain, colocando HiltAndroidRule como a regra externa.

@HiltAndroidTest
class SettingsScreenTest {

  @get:Rule
  var rule = RuleChain.outerRule(HiltAndroidRule(this)).
        around(SettingsScreenTestRule(...))

  // UI tests here.
}

Usar um ponto de entrada antes que o componente singleton esteja disponível

A anotação @EarlyEntryPoint fornece uma saída de emergência quando um ponto de entrada do Hilt precisa ser criado antes que o componente singleton esteja disponível em um teste do Hilt.

Veja mais informações sobre @EarlyEntryPoint na documentação do Hilt.

Outros recursos

Para saber mais sobre testes, consulte os seguintes recursos adicionais:

Documentação

Visualiza conteúdo