Руководство по тестированию рукояти,Руководство по тестированию рукояти

Одно из преимуществ использования фреймворков для внедрения зависимостей, таких как 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(...)
  }
}

То же самое относится к классам ViewModel, полученным путем вызова hiltViewModel() в ваших компонуемых объектах. В модульных тестах создавайте ViewModel напрямую с использованием фиктивных объектов. Информацию о том, как состояние передается из ViewModel в компонуемые объекты, см. в разделах «Состояние и Jetpack Compose» и «Куда поднимать состояние» .

Сквозные тесты

Для интеграционных тестов Hilt внедряет зависимости так же, как и в ваш рабочий код. Тестирование с помощью Hilt не требует обслуживания, поскольку Hilt автоматически генерирует новый набор компонентов для каждого теста.

Добавление зависимостей для тестирования

Чтобы использовать Hilt в тестах, добавьте в свой проект зависимость hilt-android-testing :

dependencies {
    // For Robolectric tests.
    testImplementation("com.google.dagger:hilt-android-testing:2.57.1")
    kspTest("com.google.dagger:hilt-android-compiler:2.57.1")

    // For instrumented tests.
    androidTestImplementation("com.google.dagger:hilt-android-testing:2.57.1")
    kspAndroidTest("com.google.dagger:hilt-android-compiler:2.57.1")

    // Compose UI test rule.
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")
    debugImplementation("androidx.compose.ui:ui-test-manifest")

}

Настройка тестирования пользовательского интерфейса

Необходимо аннотировать любой UI-тест, использующий Hilt, с помощью @HiltAndroidTest . Эта аннотация отвечает за генерацию компонентов Hilt для каждого теста.

Кроме того, вам необходимо добавить HiltAndroidRule в тестовый класс. Он управляет состоянием компонентов и используется для внедрения зависимостей в ваш тест:

@HiltAndroidTest
class SettingsScreenTest {

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

    @get:Rule(order = 1)
    val composeRule = createAndroidComposeRule<HiltTestActivity>()

    // Compose UI tests here.
}

Далее, вашему тесту необходимо знать о классе Application , который Hilt автоматически генерирует для вас.

Чтобы Hilt мог внедрять зависимости, необходимо создать пустую активность с именем HiltTestActivity в вашем наборе исходных файлов androidTest и аннотировать её с помощью @AndroidEntryPoint . Затем createAndroidComposeRule будет использовать эту активность в качестве хоста для вашего составного контента.

Тестовое приложение

Необходимо выполнять инструментальные тесты, использующие Hilt, в объекте Application , поддерживающем Hilt. Библиотека предоставляет HiltTestApplication для использования в тестах. Если для ваших тестов требуется другое базовое приложение, см. раздел «Пользовательское приложение для тестов» .

Необходимо настроить запуск тестового приложения в инструментальных тестах или тестах Robolectric . Приведенные ниже инструкции не относятся конкретно к Hilt, а представляют собой общие рекомендации по указанию пользовательского приложения для запуска в тестах.

Включите инструментальные тесты в тестовую среду.

Для использования приложения Hilt в инструментальных тестах необходимо настроить новый средство запуска тестов. Это позволит использовать Hilt для всех инструментальных тестов в вашем проекте. Выполните следующие шаги:

  1. Создайте пользовательский класс, наследующий AndroidJUnitRunner , в папке androidTest .
  2. Переопределите функцию 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)
    }
}

Далее настройте этот средство запуска тестов в вашем файле Gradle, как описано в руководстве по инструментальному модульному тестированию . Убедитесь, что вы используете полный путь к классам:

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 SettingsScreenTest {

  @get:Rule
  var hiltRule = HiltAndroidRule(this)

  // Robolectric tests here.
}

Функции тестирования

После того как Hilt будет готов к использованию в ваших тестах, вы сможете использовать несколько функций для настройки процесса тестирования.

Внедрение типов в тесты

Для внедрения типов в тест используйте аннотацию @Inject для внедрения полей. Чтобы указать Hilt заполнить поля, @Inject , вызовите метод hiltRule.inject() .

См. следующий пример инструментального теста:

@HiltAndroidTest
class SettingsScreenTest {

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

    @get:Rule(order = 1)
    val composeRule = createAndroidComposeRule<HiltTestActivity>()

    @Inject
    lateinit var analyticsAdapter: AnalyticsAdapter

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

    @Test
    fun settingsScreen_showsTitle() {
        composeRule.setContent {
            SettingsScreen()
        }
        composeRule.onNodeWithText("Settings").assertIsDisplayed()
        // analyticsRepository is available here.
    }
}

Заменить переплет

Если вам нужно внедрить фиктивный или имитированный экземпляр зависимости, вам необходимо указать Hilt не использовать привязку, которая использовалась в рабочем коде, а использовать другую. Чтобы заменить привязку, вам нужно заменить модуль, содержащий эту привязку, тестовым модулем, содержащим привязки, которые вы хотите использовать в тесте.

Например, предположим, что в вашем рабочем коде привязка для AnalyticsService объявлена ​​следующим образом:

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

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

Чтобы заменить привязку AnalyticsService в тестах, создайте новый модуль Hilt в папке test или androidTest с фиктивной зависимостью и аннотируйте его с помощью @TestInstallIn . Все тесты в этой папке будут получать доступ к фиктивной зависимости.

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

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

Поскольку компонуемые объекты обычно используют эти зависимости косвенно через ViewModel, полученный с помощью hiltViewModel() , достаточно заменить привязку в Hilt. Тестируемый компонуемый объект автоматически обнаруживает поддельную зависимость.

Заменить привязку в одном тесте

Чтобы заменить привязку только в одном тесте, а не во всех, удалите модуль Hilt из теста с помощью аннотации @UninstallModules и создайте новый тестовый модуль внутри теста.

Следуя примеру AnalyticsService из предыдущей версии, начните с того, что укажите Hilt игнорировать производственный модуль, используя аннотацию @UninstallModules в тестовом классе:

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

Далее необходимо заменить привязку. Создайте новый модуль внутри тестового класса, который определяет привязку теста:

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

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

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

  // ...
}

Это заменяет привязку только для одного тестового класса. Если вы хотите заменить привязку для всех тестовых классов, используйте аннотацию @TestInstallIn из раздела выше. В качестве альтернативы вы можете поместить привязку теста в модуль test для тестов Robolectric или в модуль androidTest для инструментальных тестов. Рекомендуется использовать @TestInstallIn везде, где это возможно.

Привязка новых значений

Используйте аннотацию @BindValue , чтобы легко привязать поля в ваших тестах к графу зависимостей Hilt. Аннотируйте поле @BindValue , и оно будет привязано к объявленному типу поля со всеми квалификаторами, присутствующими для этого поля.

В примере AnalyticsService вы можете заменить AnalyticsService на фиктивный объект, используя @BindValue :

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

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

  ...
}

Это упрощает как замену привязки, так и ссылку на привязку в вашем тесте, позволяя делать и то, и другое одновременно.

Аннотация @BindValue работает с квалификаторами и другими аннотациями для тестирования. Например, если вы используете такие библиотеки для тестирования, как Mockito , вы можете использовать её в тесте Robolectric следующим образом:

...
class SettingsScreenTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

Если вам нужно добавить множественную привязку , вы можете использовать аннотации @BindValueIntoSet и @BindValueIntoMap вместо @BindValue . Для использования @BindValueIntoMap необходимо также аннотировать поле аннотацией ключа карты.

Особые случаи

Hilt также предоставляет функции для поддержки нестандартных сценариев использования.

Специальное приложение для тестирования

Если вы не можете использовать HiltTestApplication потому что ваше тестовое приложение должно расширять другое приложение, аннотируйте новый класс или интерфейс с помощью @CustomTestApplication , передав значение базового класса, который вы хотите, чтобы сгенерированное приложение Hilt расширяло.

@CustomTestApplication сгенерирует класс Application , готовый для тестирования с помощью Hilt, который расширяет класс `Application`, переданный вами в качестве параметра.

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

В приведенном примере Hilt генерирует Application с именем HiltTestApplication_Application , которое расширяет класс BaseApplication . Как правило, имя сгенерированного приложения — это имя аннотированного класса с добавлением _Application . Необходимо настроить сгенерированное тестовое приложение Hilt для запуска в инструментальных тестах или тестах Robolectric , как описано в разделе «Тестовое приложение» .

Несколько объектов TestRule в вашем инструментированном тесте

В тестах Compose UI уже используется комбинация HiltAndroidRule и правила теста Compose, например, createAndroidComposeRule . Если у вас есть дополнительные объекты TestRule , убедитесь, что HiltAndroidRule выполняется первым. Объявите порядок выполнения с помощью атрибута order в @Rule :

@HiltAndroidTest
class SettingsScreenTest {

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

  @get:Rule(order = 1)
  val composeRule = createAndroidComposeRule<HiltTestActivity>()

  @get:Rule(order = 2)
  val otherRule = SomeOtherRule()

  // UI tests here.
}

В качестве альтернативы, вы можете обернуть правила с помощью RuleChain , поместив HiltAndroidRule в качестве внешнего правила.

@HiltAndroidTest
class SettingsScreenTest {

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

  // UI tests here.
}

Используйте точку входа до того, как станет доступен компонент-синглтон.

Аннотация @EarlyEntryPoint предоставляет возможность избежать проблем, когда точку входа Hilt необходимо создать до того, как компонент-синглтон станет доступен в тесте Hilt.

Более подробная информация об @EarlyEntryPoint содержится в документации Hilt .

Дополнительные ресурсы

Для получения более подробной информации о тестировании ознакомьтесь со следующими дополнительными ресурсами:

Документация

Просмотры контента