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

Одним из преимуществ использования фреймворков внедрения зависимостей, таких как 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 для использования в тестах. Если для ваших тестов требуется другое базовое приложение, см. раздел Пользовательское приложение для тестов .

Вам необходимо настроить тестовое приложение для запуска в инструментальных тестах или тестах 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)
    }
}

Ява

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

Котлин

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

Невозможно использовать launchFragmentInContainer из библиотеки androidx.fragment:fragment-testing с Hilt, поскольку он полагается на активность, которая не аннотирована @AndroidEntryPoint .

Вместо этого используйте код launchFragmentInHiltContainer из репозитория architecture-samples на GitHub.

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

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

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