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:

Kotlin

@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(...)
  }
}

Java

@ActivityScope
public class AnalyticsAdapter {

  private final AnalyticsService analyticsService;

  @Inject
  AnalyticsAdapter(AnalyticsService analyticsService) {
    this.analyticsService = analyticsService;
  }
}

public final class AnalyticsAdapterTest {

  @Test
  public void happyPath() {
    // You don't need Hilt to create an instance of AnalyticsAdapter.
    // You can pass a fake or mock AnalyticsService.
    AnalyticsAdapter adapter = new AnalyticsAdapter(fakeAnalyticsService);
    assertEquals(...);
  }
}

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:

Groovy

dependencies {
    // For Robolectric tests.
    testImplementation 'com.google.dagger:hilt-android-testing:2.51.1'
    // ...with Kotlin.
    kaptTest 'com.google.dagger:hilt-android-compiler:2.51.1'
    // ...with Java.
    testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.51.1'


    // For instrumented tests.
    androidTestImplementation 'com.google.dagger:hilt-android-testing:2.51.1'
    // ...with Kotlin.
    kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.51.1'
    // ...with Java.
    androidTestAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.51.1'
}

Kotlin

dependencies {
    // For Robolectric tests.
    testImplementation("com.google.dagger:hilt-android-testing:2.51.1")
    // ...with Kotlin.
    kaptTest("com.google.dagger:hilt-android-compiler:2.51.1")
    // ...with Java.
    testAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.51.1")


    // For instrumented tests.
    androidTestImplementation("com.google.dagger:hilt-android-testing:2.51.1")
    // ...with Kotlin.
    kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.51.1")
    // ...with Java.
    androidTestAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.51.1")
}

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:

Kotlin

@HiltAndroidTest
class SettingsActivityTest {

  @get:Rule
  var hiltRule = HiltAndroidRule(this)

  // UI tests here.
}

Java

@HiltAndroidTest
public final class SettingsActivityTest {

  @Rule
  public HiltAndroidRule hiltRule = new HiltAndroidRule(this);

  // UI tests here.
}

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

Aplicativo de teste

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. Modifique a função newApplication e transmita o nome do aplicativo de teste Hilt gerado.

Kotlin

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

Java

// A custom runner to set up the instrumented application class for tests.
public final class CustomTestRunner extends AndroidJUnitRunner {

  @Override
  public Application newApplication(ClassLoader cl, String className, Context context)
      throws ClassNotFoundException, IllegalAccessException, InstantiationException {
    return super.newApplication(cl, HiltTestApplication.class.getName(), 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:

Groovy

android {
    defaultConfig {
        // Replace com.example.android.dagger with your class path.
        testInstrumentationRunner "com.example.android.dagger.CustomTestRunner"
    }
}

Kotlin

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:

Kotlin

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

  @get:Rule
  var hiltRule = HiltAndroidRule(this)

  // Robolectric tests here.
}

Java

@HiltAndroidTest
@Config(application = HiltTestApplication.class)
class SettingsActivityTest {

  @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this);

  // Robolectric tests here.
}

Se você usa uma versão do Plug-in do Android para Gradle anterior à 4.2, ative a transformação de classes @AndroidEntryPoint em testes de unidade locais aplicando a seguinte configuração no arquivo build.gradle do módulo:

Groovy

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

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

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:

Kotlin

@HiltAndroidTest
class SettingsActivityTest {

  @get:Rule
  var hiltRule = HiltAndroidRule(this)

  @Inject
  lateinit var analyticsAdapter: AnalyticsAdapter

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

  @Test
  fun `happy path`() {
    // Can already use analyticsAdapter here.
  }
}

Java

@HiltAndroidTest
public final class SettingsActivityTest {

  @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this);

  @Inject AnalyticsAdapter analyticsAdapter;

  @Before
  public void init() {
    hiltRule.inject();
  }

  @Test
  public void happyPath() {
    // Can already use analyticsAdapter 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:

Kotlin

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

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

Java

@Module
@InstallIn(SingletonComponent.class)
public abstract class AnalyticsModule {

  @Singleton
  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

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.

Kotlin

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

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

Java

@Module
@TestInstallIn(
    components = SingletonComponent.class,
    replaces = AnalyticsModule.class
)
public abstract class FakeAnalyticsModule {

  @Singleton
  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    FakeAnalyticsService fakeAnalyticsService
  );
}

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:

Kotlin

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

Java

@UninstallModules(AnalyticsModule.class)
@HiltAndroidTest
public final class SettingsActivityTest { ... }

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

Kotlin

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

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

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

  ...
}

Java

@UninstallModules(AnalyticsModule.class)
@HiltAndroidTest
public final class SettingsActivityTest {

  @Module
  @InstallIn(SingletonComponent.class)
  public abstract class TestModule {

    @Singleton
    @Binds
    public abstract AnalyticsService bindAnalyticsService(
      FakeAnalyticsService fakeAnalyticsService
    );
  }
  ...
}

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:

Kotlin

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

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

  ...
}

Java

@UninstallModules(AnalyticsModule.class)
@HiltAndroidTest
class SettingsActivityTest {

  @BindValue 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:

Kotlin

...
class SettingsActivityTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

Java

...
class SettingsActivityTest {
  ...
  @BindValue @ExampleQualifier @Mock ExampleCustomType qualifiedVariable;

  // 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.

Kotlin

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

Java

@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

Se você tiver outros objetos TestRule no teste, há várias maneiras de garantir que todas as regras funcionem juntas.

É possível unir as regras da seguinte maneira:

Kotlin

@HiltAndroidTest
class SettingsActivityTest {

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

  // UI tests here.
}

Java

@HiltAndroidTest
public final class SettingsActivityTest {

  @Rule public RuleChain rule = RuleChain.outerRule(new HiltAndroidRule(this))
        .around(new SettingsActivityTestRule(...));

  // UI tests here.
}

Como alternativa, você pode usar as duas regras no mesmo nível, desde que a HiltAndroidRule seja executada primeiro. Especifique a ordem de execução usando o atributo order na anotação @Rule. Isso funciona apenas na versão 4.13 ou mais recente do JUnit:

Kotlin

@HiltAndroidTest
class SettingsActivityTest {

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

  @get:Rule(order = 1)
  var settingsActivityTestRule = SettingsActivityTestRule(...)

  // UI tests here.
}

Java

@HiltAndroidTest
public final class SettingsActivityTest {

  @Rule(order = 0)
  public HiltAndroidRule hiltRule = new HiltAndroidRule(this);

  @Rule(order = 1)
  public SettingsActivityTestRule settingsActivityTestRule = new SettingsActivityTestRule(...);

  // UI tests here.
}

launchFragmentInContainer

Não é possível usar o launchFragmentInContainer da biblioteca androidx.fragment:fragment-testing com o Hilt, porque ele depende de uma atividade que não tem a anotação @AndroidEntryPoint.

Use o código launchFragmentInHiltContainer do repositório architecture-samples do GitHub.

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.