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:
- Crea una clase personalizada que extienda
AndroidJUnitRunner
en la carpetaandroidTest
. - 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.