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

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

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'
}

Котлин

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

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

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

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

Котлин

@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 для всех инструментальных тестов в вашем проекте. Выполните следующие шаги:

  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)
    }
}

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

Котлин

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.
}

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
}

Котлин

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.
  }
}

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 объявлена ​​следующим образом:

Котлин

@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 . Все тесты в этой папке будут получать доступ к фиктивной зависимости.

Котлин

@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 в тестовом классе:

Котлин

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

Java

@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
  }

  ...
}

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 :

Котлин

@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 следующим образом:

Котлин

...
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, который расширяет класс `Application`, переданный вами в качестве параметра.

Котлин

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

Java

@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.
}

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 или выше:

Котлин

@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.

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

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

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