Основные понятия и реализация Jetpack Compose
Одно из преимуществ использования фреймворков внедрения зависимостей, таких как Hilt, заключается в том, что они упрощают тестирование кода.
Модульные тесты
Hilt не требуется для модульного тестирования, поскольку при тестировании класса, использующего внедрение через конструктор, вам не нужно использовать Hilt для создания экземпляра этого класса. Вместо этого вы можете напрямую вызвать конструктор класса, передав в него поддельные или макетные зависимости, как если бы конструктор не был аннотирован:
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(...); } }
Сквозные тесты
Для интеграционных тестов Hilt внедряет зависимости так же, как и в рабочем коде. Тестирование с помощью Hilt не требует обслуживания, поскольку Hilt автоматически генерирует новый набор компонентов для каждого теста.
Добавление зависимостей для тестирования
Чтобы использовать Hilt в тестах, добавьте в проект зависимость hilt-android-testing:
Яркий
dependencies { // For Robolectric tests. testImplementation 'com.google.dagger:hilt-android-testing:2.57.1' // ...with Kotlin. kaptTest 'com.google.dagger:hilt-android-compiler:2.57.1' // ...with Java. testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.57.1' // For instrumented tests. androidTestImplementation 'com.google.dagger:hilt-android-testing:2.57.1' // ...with Kotlin. kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.57.1' // ...with Java. androidTestAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.57.1' }
Kotlin
dependencies { // For Robolectric tests. testImplementation("com.google.dagger:hilt-android-testing:2.57.1") // ...with Kotlin. kaptTest("com.google.dagger:hilt-android-compiler:2.57.1") // ...with Java. testAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.57.1") // For instrumented tests. androidTestImplementation("com.google.dagger:hilt-android-testing:2.57.1") // ...with Kotlin. kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.57.1") // ...with Java. androidTestAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.57.1") }
Настройка тестирования интерфейса
Вы должны аннотировать любой тест пользовательского интерфейса, который использует Hilt, с помощью @HiltAndroidTest. Эта аннотация отвечает за создание компонентов Hilt для каждого теста.
Также необходимо добавить HiltAndroidRule в тестовый класс. Он управляет состоянием компонентов и используется для внедрения в тест:
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. }
Далее в тесте нужно указать класс Application, который Hilt генерирует автоматически.
Тестовое приложение
Инструментальные тесты, в которых используется Hilt, должны выполняться в объекте Application, поддерживающем Hilt. Библиотека предоставляет HiltTestApplication для использования в тестах.
Если для тестов требуется другое базовое приложение, ознакомьтесь с разделом Специальное приложение для тестов.
Вам нужно настроить тестовое приложение для запуска в инструментальных тестах или тестах Robolectric. Приведенные ниже инструкции не относятся к Hilt, а являются общими рекомендациями по указанию пользовательского приложения для запуска в тестах.
Как настроить тестовое приложение в инструментальных тестах
Чтобы использовать тестовое приложение Hilt в инструментальных тестах, необходимо настроить новый инструмент запуска тестов. Это позволит использовать Hilt для всех инструментальных тестов в проекте. Выполните следующие действия:
- Создайте собственный класс, расширяющий
AndroidJUnitRunner, в папкеandroidTest. - Переопределите функцию
newApplicationи передайте название сгенерированного тестового приложения Hilt.
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); } }
Затем настройте этот инструмент запуска тестов в файле Gradle, как описано в руководстве по инструментированному модульному тестированию. Убедитесь, что вы указали полный путь к классу:
Яркий
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" } }
Как настроить тестовое приложение в тестах Robolectric
Если вы используете Robolectric для тестирования уровня пользовательского интерфейса, то можете указать, какое приложение использовать, в файле robolectric.properties:
application = dagger.hilt.android.testing.HiltTestApplication
Вы также можете настроить приложение для каждого теста отдельно, используя аннотацию @Config 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. }
Если вы используете плагин Android Gradle версии ниже 4.2, включите преобразование классов @AndroidEntryPoint в локальных модульных тестах, применив следующую конфигурацию в файле build.gradle вашего модуля:
Яркий
hilt { enableTransformForLocalTests = true }
Kotlin
hilt { enableTransformForLocalTests = true }
Дополнительную информацию о enableTransformForLocalTests можно найти в документации по Hilt.
Функции тестирования
Когда Hilt будет готов к использованию в тестах, вы сможете использовать несколько функций для настройки процесса тестирования.
Внедрение типов в тесты
Чтобы внедрить типы в тест, используйте @Inject для внедрения через поле. Чтобы указать Hilt заполнить поля @Inject, вызовите hiltRule.inject().
Пример инструментального теста:
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. } }
Как заменить привязку
Если вам нужно внедрить поддельный или макетный экземпляр зависимости, вам нужно указать Hilt не использовать привязку, которая использовалась в рабочем коде, а вместо этого использовать другую. Чтобы заменить привязку, вам нужно заменить модуль, содержащий привязку, тестовым модулем, содержащим привязки, которые вы хотите использовать в тесте.
Например, предположим, что ваш рабочий код объявляет привязку для AnalyticsService следующим образом:
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 ); }
Чтобы заменить привязку AnalyticsService в тестах, создайте новый модуль Hilt в папке test или androidTest с фиктивной зависимостью и добавьте к нему аннотацию @TestInstallIn. Вместо этого во все тесты в этой папке внедряется поддельная зависимость.
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 ); }
Как заменить привязку в одном тесте
Чтобы заменить привязку в одном тесте, а не во всех, удалите модуль Hilt из теста с помощью аннотации @UninstallModules и создайте в тесте новый тестовый модуль.
Продолжим пример с AnalyticsService из предыдущей версии. Сначала укажите Hilt игнорировать рабочий модуль, используя аннотацию @UninstallModules в тестовом классе:
Kotlin
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsActivityTest { ... }
Java
@UninstallModules(AnalyticsModule.class) @HiltAndroidTest public final class SettingsActivityTest { ... }
Затем необходимо заменить привязку. Создайте новый модуль в тестовом классе, который определяет тестовую привязку:
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 ); } ... }
Это заменяет привязку только для одного тестового класса. Если вы хотите заменить привязку для всех тестовых классов, используйте аннотацию @TestInstallIn из раздела выше. Вы также можете поместить тестовую привязку в модуль test для тестов Robolectric или в модуль androidTest для инструментальных тестов.
Рекомендуем по возможности использовать @TestInstallIn.
Привязка новых значений
Используйте аннотацию @BindValue, чтобы легко привязать поля в тесте к графу зависимостей Hilt. Добавьте к полю аннотацию @BindValue, и оно будет связано с объявленным типом поля и всеми квалификаторами, которые для него заданы.
В примере с AnalyticsService вы можете заменить AnalyticsService на поддельный объект, используя @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(); ... }
Это упрощает как замену привязки, так и ссылку на привязку в вашем тесте, позволяя вам делать и то, и другое одновременно.
@BindValue работает с квалификаторами и другими аннотациями тестирования. Например, если вы используете библиотеки тестирования, такие как Mockito, вы можете использовать их в тесте Robolectric следующим образом:
Kotlin
... class SettingsActivityTest { ... @BindValue @ExampleQualifier @Mock lateinit var qualifiedVariable: ExampleCustomType // Robolectric tests here }
Java
... class SettingsActivityTest { ... @BindValue @ExampleQualifier @Mock ExampleCustomType qualifiedVariable; // Robolectric tests here }
Если вам нужно добавить мультипривязку, вы можете использовать аннотации @BindValueIntoSet и @BindValueIntoMap вместо @BindValue. @BindValueIntoMap требует, чтобы вы также аннотировали поле с помощью аннотации ключа карты.
Особые случаи
Hilt также предоставляет функции для поддержки нестандартных сценариев использования.
Собственное приложение для тестирования
Если вы не можете использовать HiltTestApplication, потому что тестовое приложение должно расширять другое приложение, добавьте аннотацию @CustomTestApplication к новому классу или интерфейсу, передав значение базового класса, который должно расширять сгенерированное приложение Hilt.
@CustomTestApplication сгенерирует класс Application, готовый к тестированию с помощью Hilt, который расширяет приложение, переданное в качестве параметра.
Kotlin
@CustomTestApplication(BaseApplication::class) interface HiltTestApplication
Java
@CustomTestApplication(BaseApplication.class) interface HiltTestApplication { }
В примере Hilt генерирует Application с именем HiltTestApplication_Application, который расширяет класс BaseApplication. Как правило, название сгенерированного приложения – это название аннотированного класса с добавлением _Application. Вам нужно настроить запуск сгенерированного тестового приложения Hilt в инструментальных тестах или тестах Robolectric, как описано в разделе Тестовое приложение.
Несколько объектов TestRule в инструментальном тесте
Если в вашем тесте есть другие объекты TestRule, вы можете обеспечить совместную работу всех правил несколькими способами.
Вы можете объединить правила следующим образом:
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. }
Вы также можете использовать оба правила на одном уровне, если HiltAndroidRule выполняется первым. Укажите порядок выполнения с помощью атрибута order в аннотации @Rule. Это работает только в JUnit версии 4.13 или более поздней:
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
Использовать launchFragmentInContainer из библиотеки androidx.fragment:fragment-testing с Hilt нельзя, поскольку он зависит от действия, не аннотированного с помощью @AndroidEntryPoint.
Используйте код launchFragmentInHiltContainer из репозитория architecture-samples на GitHub.