Guía de prueba de Hilt

Uno de los beneficios de usar frameworks de inyección de dependencias, como Hilt, es que facilita la prueba del código.

Pruebas de unidades

No es necesario usar Hilt para las pruebas de unidades, ya que, cuando se prueba una clase que usa la inyección de constructor, no necesitas usar Hilt para crear una instancia de esa clase. En su lugar, puedes llamar directamente a un constructor de clase pasando dependencias falsas o simuladas, tal como lo harías si el constructor no estuviera 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(...);
  }
}

Pruebas de extremo a extremo

En el caso de las pruebas de integración, Hilt inserta las dependencias como lo haría en tu código de producción. Las pruebas con Hilt no requieren mantenimiento, ya que se genera automáticamente un nuevo conjunto de componentes para cada prueba.

Cómo agregar dependencias de prueba

Para usar Hilt en tus pruebas, incluye la dependencia hilt-android-testing en tu proyecto:

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

Cómo configurar la prueba de IU

Debes anotar cualquier prueba de IU que use Hilt con @HiltAndroidTest. Esta anotación es responsable de generar los componentes de Hilt para cada prueba.

Además, debes agregar HiltAndroidRule a la clase de prueba. Administra el estado de los componentes y se usa para realizar la inyección en tu prueba:

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

Luego, tu prueba necesita información sobre la clase Application que Hilt genera automáticamente.

Aplicación de prueba

Debes ejecutar pruebas instrumentadas que usen Hilt en un objeto Application que sea compatible con Hilt. La biblioteca proporciona un objeto HiltTestApplication para usar en pruebas. Si tus pruebas necesitan una aplicación de base diferente, consulta Aplicación personalizada para pruebas.

Debes configurar tu aplicación de prueba para que se ejecute en tus pruebas instrumentadas o pruebas de Robolectric. Las siguientes instrucciones no se aplican solo a Hilt, sino que son lineamientos generales sobre cómo especificar una aplicación personalizada para ejecutar en pruebas.

Cómo configurar la aplicación de prueba en pruebas instrumentadas

Para usar la aplicación de prueba de Hilt en pruebas instrumentadas, debes configurar un nuevo ejecutor de pruebas. De esta manera, Hilt funcionará en todas las pruebas instrumentadas de tu proyecto. Completa los pasos siguientes:

  1. Crea una clase personalizada que extienda AndroidJUnitRunner en la carpeta androidTest.
  2. Anula la función newApplication y pasa el nombre de la aplicación de prueba de Hilt que se generó.

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

Luego, configura este ejecutor de pruebas en tu archivo de Gradle como se describe en la guía de pruebas de unidades instrumentadas. Asegúrate de usar la ruta de clase completa:

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"
    }
}
Cómo configurar la aplicación de prueba en las pruebas de Robolectric

Si usas Robolectric para probar tu capa de IU, puedes especificar qué aplicación deseas usar en el archivo robolectric.properties:

application = dagger.hilt.android.testing.HiltTestApplication

Como alternativa, puedes configurar la aplicación en cada prueba por separado con la anotación @Config de 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.
}

Si usas una versión del complemento de Android para Gradle anterior a 4.2, habilita la transformación de clases @AndroidEntryPoint en las pruebas de unidades locales aplicando la siguiente configuración en el archivo build.gradle del módulo:

Groovy

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

Obtén más información sobre enableTransformForLocalTests en la documentación de Hilt.

Funciones de prueba

Una vez que Hilt esté listo para que lo uses en tus pruebas, puedes usar varias funciones para personalizar el proceso de prueba.

Cómo insertar tipos en las pruebas

Si deseas insertar tipos en una prueba, usa @Inject para la inyección de campo. Para indicarle a Hilt que propague los campos @Inject, llama a hiltRule.inject().

Consulta el siguiente ejemplo de una prueba instrumentada:

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

Cómo reemplazar una vinculación

Si necesitas insertar una instancia falsa de una dependencia, debes indicarle a Hilt que no use la vinculación que usó en el código de producción y que use una diferente. Para reemplazar una vinculación, debes reemplazar el módulo que la contiene con un módulo de prueba que contenga las vinculaciones que deseas usar en la prueba.

Por ejemplo, supongamos que tu código de producción declara una vinculación para AnalyticsService de la siguiente manera:

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 reemplazar la vinculación de AnalyticsService en las pruebas, crea un nuevo módulo de Hilt en la carpeta test o androidTest con la dependencia falsa y anótala con @TestInstallIn. Todas las pruebas en esa carpeta se insertarán con la dependencia 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
  );
}

Reemplaza una vinculación en una sola prueba

Para reemplazar una vinculación en una sola prueba en lugar de todas las pruebas, desinstala un módulo de Hilt de una prueba con la anotación @UninstallModules y crea un módulo de prueba nuevo dentro de la prueba.

Siguiendo el ejemplo de AnalyticsService de la versión anterior, primero, comienza por indicarle a Hilt que ignore el módulo de producción con la anotación @UninstallModules en la clase de prueba:

Kotlin

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

Java

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

Luego, debes reemplazar la vinculación. Crea un módulo nuevo dentro de la clase de prueba, en el que se defina la vinculación de prueba:

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
    );
  }
  ...
}

Con esto, solo se reemplaza la vinculación de una clase de prueba. Si deseas reemplazar la vinculación de todas las clases de prueba, usa la anotación @TestInstallIn de la sección anterior. Como alternativa, puedes colocar la vinculación de prueba en el módulo test para pruebas de Robolectric o en el módulo androidTest para pruebas instrumentadas. Se recomienda usar @TestInstallIn siempre que sea posible.

Cómo vincular valores nuevos

Usa la anotación @BindValue para vincular fácilmente los campos de tu prueba en el gráfico de dependencia de Hilt. Anota un campo con @BindValue y se vinculará bajo el tipo de campo declarado con cualquier calificador que esté presente para ese campo.

En el ejemplo de AnalyticsService, puedes reemplazar AnalyticsService por uno 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();

  ...
}

Con esto, se simplifica el reemplazo de una vinculación y la referencia a una vinculación en tu prueba, ya que te permite realizar ambas acciones al mismo tiempo.

@BindValue funciona con calificadores y otras anotaciones de prueba. Por ejemplo, si usas bibliotecas de prueba como Mockito, puedes usarlo en una prueba de Robolectric de la siguiente manera:

Kotlin

...
class SettingsActivityTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

Java

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

  // Robolectric tests here
}

Si necesitas agregar una vinculación múltiple, puedes usar las anotaciones @BindValueIntoSet y @BindValueIntoMap en lugar de @BindValue. En cuanto a @BindValueIntoMap, también requiere que anotes el campo con una anotación de clave del mapa.

Casos especiales

Hilt también ofrece funciones para admitir casos prácticos no estándar.

Aplicación personalizada para pruebas

Si no puedes usar HiltTestApplication porque tu aplicación de prueba necesita extender otra aplicación, anota una nueva clase o interfaz con @CustomTestApplication y pasa el valor de la clase de base que deseas que la aplicación generada por Hilt extienda.

@CustomTestApplication generará una Application lista para pruebas con Hilt que extiende la aplicación que pasaste como parámetro.

Kotlin

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

Java

@CustomTestApplication(BaseApplication.class)
interface HiltTestApplication { }

En el ejemplo, Hilt genera una Application, llamada HiltTestApplication_Application, que extiende la clase BaseApplication. En general, el nombre de la aplicación que se genera es el nombre de la clase anotada que se agregó con _Application. Debes configurar la aplicación de prueba generada por Hilt para que se ejecute en tus pruebas instrumentadas o pruebas de Robolectric como se describe en Aplicación de prueba.

Varios objetos TestRule en tu prueba instrumentada

Si tienes otros objetos TestRule en tu prueba, hay varias formas de garantizar que todas las reglas funcionen en conjunto.

Puedes unir las reglas de la siguiente manera:

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, puedes usar ambas reglas en el mismo nivel, siempre y cuando se ejecute HiltAndroidRule primero. Especifica el orden de ejecución usando el atributo order en la anotación @Rule. Esto solo funciona en JUnit 4.13 o versiones posteriores:

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

No es posible usar launchFragmentInContainer de la biblioteca androidx.fragment:fragment-testing con Hilt, ya que depende de una actividad que no está anotada con @AndroidEntryPoint.

En su lugar, usa el código launchFragmentInHiltContainer del repositorio de GitHub architecture-samples.

Usa un punto de entrada antes de que el componente singleton esté disponible

La anotación @EarlyEntryPoint proporciona una salida de escape cuando se debe crear un punto de entrada de Hilt antes de que el componente singleton esté disponible en una prueba de Hilt.

Obtén más información sobre @EarlyEntryPoint en la documentación de Hilt.