Одним из преимуществ использования фреймворков внедрения зависимостей, таких как Hilt, является то, что они упрощают тестирование кода.
Тесты модулей
Hilt не нужен для модульных тестов, так как при тестировании класса, использующего инъекцию конструктора, вам не нужно использовать Hilt для создания экземпляра этого класса. Вместо этого вы можете напрямую вызвать конструктор класса, передав поддельные или фиктивные зависимости, как если бы конструктор не был аннотирован:
Котлин
@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(...) } }
Ява
@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.56.2' // ...with Kotlin. kaptTest 'com.google.dagger:hilt-android-compiler:2.56.2' // ...with Java. testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.56.2' // For instrumented tests. androidTestImplementation 'com.google.dagger:hilt-android-testing:2.56.2' // ...with Kotlin. kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.56.2' // ...with Java. androidTestAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.56.2' }
Котлин
dependencies { // For Robolectric tests. testImplementation("com.google.dagger:hilt-android-testing:2.56.2") // ...with Kotlin. kaptTest("com.google.dagger:hilt-android-compiler:2.56.2") // ...with Java. testAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.56.2") // For instrumented tests. androidTestImplementation("com.google.dagger:hilt-android-testing:2.56.2") // ...with Kotlin. kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.56.2") // ...with Java. androidTestAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.56.2") }
Настройка теста пользовательского интерфейса
Вы должны аннотировать любой тест пользовательского интерфейса, который использует Hilt, с помощью @HiltAndroidTest
. Эта аннотация отвечает за генерацию компонентов Hilt для каждого теста.
Также вам нужно добавить HiltAndroidRule
в тестовый класс. Он управляет состоянием компонентов и используется для выполнения инъекции в ваш тест:
Котлин
@HiltAndroidTest class SettingsActivityTest { @get:Rule var hiltRule = HiltAndroidRule(this) // UI tests here. }
Ява
@HiltAndroidTest public final class SettingsActivityTest { @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this); // UI tests here. }
Далее вашему тесту необходимо знать о классе Application
, который Hilt автоматически генерирует для вас.
Тестовое приложение
Вы должны выполнить инструментированные тесты, которые используют Hilt в объекте Application
, который поддерживает Hilt. Библиотека предоставляет HiltTestApplication
для использования в тестах. Если вашим тестам требуется другое базовое приложение, см. Custom application for tests .
Вы должны настроить свое тестовое приложение для запуска в инструментальных тестах или тестах Robolectric . Следующие инструкции не являются специфическими для Hilt, но представляют собой общие рекомендации по указанию пользовательского приложения для запуска в тестах.
Установите тестовое приложение в инструментальных тестах
Чтобы использовать приложение Hilt test в инструментированных тестах , вам необходимо настроить нового исполнителя тестов. Это позволит Hilt работать со всеми инструментированными тестами в вашем проекте. Выполните следующие шаги:
- Создайте пользовательский класс, расширяющий
AndroidJUnitRunner
в папкеandroidTest
. - Переопределите функцию
newApplication
и передайте имя сгенерированного тестового приложения Hilt.
Котлин
// 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) } }
Ява
// 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, как описано в руководстве по инструментированному модульному тестированию . Убедитесь, что вы используете полный classpath:
Круто
android { defaultConfig { // Replace com.example.android.dagger with your class path. testInstrumentationRunner "com.example.android.dagger.CustomTestRunner" } }
Котлин
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:
Котлин
@HiltAndroidTest @Config(application = HiltTestApplication::class) class SettingsActivityTest { @get:Rule var hiltRule = HiltAndroidRule(this) // Robolectric tests here. }
Ява
@HiltAndroidTest @Config(application = HiltTestApplication.class) class SettingsActivityTest { @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this); // Robolectric tests here. }
Если вы используете версию Android Gradle Plugin ниже 4.2, включите преобразование классов @AndroidEntryPoint
в локальных модульных тестах, применив следующую конфигурацию в файле build.gradle
вашего модуля:
Круто
hilt { enableTransformForLocalTests = true }
Котлин
hilt { enableTransformForLocalTests = true }
Более подробную информацию о enableTransformForLocalTests
можно найти в документации Hilt .
Тестирование функций
Как только Hilt будет готов к использованию в ваших тестах, вы сможете использовать несколько функций для настройки процесса тестирования.
Типы инъекций в тестах
Чтобы ввести типы в тест, используйте @Inject
для внедрения полей. Чтобы указать Hilt заполнить поля @Inject
, вызовите hiltRule.inject()
.
См. следующий пример инструментального теста:
Котлин
@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. } }
Ява
@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
следующим образом:
Котлин
@Module @InstallIn(SingletonComponent::class) abstract class AnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( analyticsServiceImpl: AnalyticsServiceImpl ): AnalyticsService }
Ява
@Module @InstallIn(SingletonComponent.class) public abstract class AnalyticsModule { @Singleton @Binds public abstract AnalyticsService bindAnalyticsService( AnalyticsServiceImpl analyticsServiceImpl ); }
Чтобы заменить привязку AnalyticsService
в тестах, создайте новый модуль Hilt в папке test
или androidTest
с поддельной зависимостью и аннотируйте его с помощью @TestInstallIn
. Вместо этого все тесты в этой папке внедряются с поддельной зависимостью.
Котлин
@Module @TestInstallIn( components = [SingletonComponent::class], replaces = [AnalyticsModule::class] ) abstract class FakeAnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService }
Ява
@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
в тестовом классе:
Котлин
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsActivityTest { ... }
Ява
@UninstallModules(AnalyticsModule.class) @HiltAndroidTest public final class SettingsActivityTest { ... }
Далее необходимо заменить привязку. Создайте новый модуль в тестовом классе, который определяет тестовую привязку:
Котлин
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsActivityTest { @Module @InstallIn(SingletonComponent::class) abstract class TestModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService } ... }
Ява
@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
:
Котлин
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsActivityTest { @BindValue @JvmField val analyticsService: AnalyticsService = FakeAnalyticsService() ... }
Ява
@UninstallModules(AnalyticsModule.class) @HiltAndroidTest class SettingsActivityTest { @BindValue AnalyticsService analyticsService = FakeAnalyticsService(); ... }
Это упрощает как замену привязки, так и ссылку на привязку в вашем тесте, позволяя вам делать это одновременно.
@BindValue
работает с квалификаторами и другими аннотациями тестирования. Например, если вы используете библиотеки тестирования, такие как Mockito , вы можете использовать ее в тесте Robolectric следующим образом:
Котлин
... class SettingsActivityTest { ... @BindValue @ExampleQualifier @Mock lateinit var qualifiedVariable: ExampleCustomType // Robolectric tests here }
Ява
... class SettingsActivityTest { ... @BindValue @ExampleQualifier @Mock ExampleCustomType qualifiedVariable; // Robolectric tests here }
Если вам необходимо добавить мультисвязь , вы можете использовать аннотации @BindValueIntoSet
и @BindValueIntoMap
вместо @BindValue
. @BindValueIntoMap
требует, чтобы вы также аннотировали поле с помощью аннотации ключа карты.
Особые случаи
Hilt также предоставляет функции для поддержки нестандартных вариантов использования.
Пользовательское приложение для тестов
Если вы не можете использовать HiltTestApplication
, поскольку вашему тестовому приложению необходимо расширить другое приложение, аннотируйте новый класс или интерфейс с помощью @CustomTestApplication
, передав значение базового класса, который должно расширять сгенерированное приложение Hilt.
@CustomTestApplication
сгенерирует класс Application
, готовый к тестированию с помощью Hilt, который расширяет приложение, переданное вами в качестве параметра.
Котлин
@CustomTestApplication(BaseApplication::class) interface HiltTestApplication
Ява
@CustomTestApplication(BaseApplication.class) interface HiltTestApplication { }
В этом примере Hilt генерирует Application
с именем HiltTestApplication_Application
, которое расширяет класс BaseApplication
. В общем случае имя сгенерированного приложения — это имя аннотированного класса с добавлением _Application
. Вы должны настроить сгенерированное тестовое приложение Hilt для запуска в инструментированных тестах или тестах Robolectric, как описано в Тестовое приложение .
Несколько объектов TestRule в вашем инструментированном тесте
Если в вашем тесте есть другие объекты TestRule
, существует несколько способов гарантировать, что все правила работают вместе.
Вы можете объединить правила следующим образом:
Котлин
@HiltAndroidTest class SettingsActivityTest { @get:Rule var rule = RuleChain.outerRule(HiltAndroidRule(this)). around(SettingsActivityTestRule(...)) // UI tests here. }
Ява
@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 или выше:
Котлин
@HiltAndroidTest class SettingsActivityTest { @get:Rule(order = 0) var hiltRule = HiltAndroidRule(this) @get:Rule(order = 1) var settingsActivityTestRule = SettingsActivityTestRule(...) // UI tests here. }
Ява
@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
из библиотеки androidx.fragment:fragment-testing
с Hilt, поскольку он полагается на активность, которая не аннотирована @AndroidEntryPoint
.
Вместо этого используйте код launchFragmentInHiltContainer
из репозитория architecture-samples
на GitHub.
Используйте точку входа до того, как компонент singleton станет доступен
Аннотация @EarlyEntryPoint
обеспечивает аварийный выход, когда точку входа Hilt необходимо создать до того, как компонент singleton станет доступен в тесте Hilt.
Более подробную информацию о @EarlyEntryPoint
можно найти в документации Hilt .