Туториал по тестированию с Hilt: UI и Аппаратные (Инструментальные) Тесты

25 ноября 2021

Узнайте, как начать тестирование с Hilt, написав тесты для UI и устройств.

Dagger - одна из самых популярных библиотек для внедрения зависимостей для Java и Kotlin. Он позволяет вам декларативно определять отношения зависимости между различными компонентами, украсив ваш код аннотациями, такими как @Inject и @Component.

Каким бы ценным он ни был, Dagger - не простой. Google серьезно работает над тем, чтобы сделать его более доступным. Hilt здесь именно по этой причине - предоставить более простой способ определения внедрения зависимостей в Android.

В этом туториале вы узнаете:

  • Роль Dagger и Hilt в выполнении теста.
  • Как реализовать и запустить UI тесты с помощью Robolectric и Hilt.
  • Как упростить выполнение тестов на устройстве.
  • Что такое @UninstallModules и @BindValue и как их использовать в ваших тестах.

Вы узнаете об этом, выполнив тесты для приложения RW News.

Пришло время погрузиться!

Приступим

Загрузите и распакуйте материалы для этого туториала с помощью кнопки «Скачать материалы». Откройте стартовый проект, затем соберите и запустите. Вы увидите следующее:

Приложение RW News

Это RW News. В настоящее время в приложении нет тестов, что ж, пора их добавить.

Откройте проект RW News и посмотрите на исходную структуру кода:

Начальная структура кода RWNews.

Заметка

Файлы в androidTest в настоящее время не компилируются. Не волнуйтесь, вы исправите это позже в туториале, когда будете выполнять аппаратные тесты.

Это важные папки в проекте вместе с их содержимым:

  • androidTest: Аппаратные тесты.
  • debug: Код и тесты доступны в режиме отладки.
  • main: Основной код приложения.
  • test: Unit и Robolectric тесты.
  • testShared: Код, которым вы делитесь между разными типами тестов. В настоящее время он содержит подделки, которые вы будете использовать в этом туториале.

Реализация UI-тестов с Hilt и Robolectric

А теперь пора поработать над интересными вещами! Ваша цель - реализовать:

  1. UI-тесты с помощью Robolectric.
  2. Аппаратное тестирование.

Заметка

Что насчет unit-тестов? Как вы знаете, они являются фундаментальной частью каждого приложения, но Dagger и Hilt не помогают в их реализации. Что помогает, так это не конкретный фреймворк внедрения зависимостей, а использование самого внедрения зависимостей. Dagger и Hilt действительно полезны, когда вы реализуете UI-тест с помощью Robolectric.

Robolectric - это среда тестирования, которая позволяет вам выполнять и запускать тесты, зависящие от среды Android, без фактической реализации платформы Android. Это позволяет запускать UI-тесты на JVM без создания экземпляров эмулятора Android. При использовании Robolectric тесты проходят быстрее и требуют меньше ресурсов.

В этом туториале вы создадите тест для MainActivity. Перед этим важно понять роль Dagger and Hilt, которые уже помогают вам создать дерево зависимостей объектов для вашего приложения.

Когда вы запускаете тестирование, вам нужно заменить некоторые объекты поддельной реализацией, а для этого вам понадобится другое дерево зависимостей. Как вы скоро увидите, Hilt позволяет заменять объекты, которые вы используете для приложения, другими объектами, которые вы используете только при выполнении тестов.

Настройка Robolectric

Прежде, чем вы сможете написать UI-тест, вам необходимо настроить Robolectric. Ваш первый шаг - добавить зависимости для библиотеки тестирования Hilt для Robolectric.

Откройте build.gradle из приложения и добавьте следующее определение:

// ...
dependencies {
  // ...
  // Hilt for Robolectric tests.
  testImplementation "com.google.dagger:hilt-android-testing:$hilt_android_version" // 1
  testImplementation "org.robolectric:robolectric:$robolectric_version" // 2
  kaptTest "com.google.dagger:hilt-android-compiler:$hilt_android_version" // 3
}

Здесь вы:

  1. Добавляете зависимость для использования тестирования с Hilt. Это определение для тестового типа сборки.
  2. Для того же тестового типа сборки вы добавляете зависимость в Robolectric.
  3. Используете kaptTest, чтобы установить процессор аннотаций, ответственный за генерацию тестового кода из определения Hilt.

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

Создание файла RoboMainActivityTest

В качестве примера UI-теста с Hilt и Robolectric вы протестируете MainActivity. Откройте MainActivity.kt в пакете ui и посмотрите его код:

@AndroidEntryPoint // 1
class MainActivity : AppCompatActivity() {

  @Inject
  lateinit var navigationHelper: NavigationHelper // 2

  override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    val binding = ActivityMainBinding.inflate(LayoutInflater.from(this))
    setContentView(binding.root)
    if (savedInstanceState == null) {
      navigationHelper.replace(R.id.anchor, NewsListFragment()) // 3
    }
  }
}

Это очень простое Activity, где вы:

  1. Используете @AndroidEntryPoint, чтобы пометить класс как точку входа Hilt.
  2. Определяете свойство типа NavigationHelper, инициализируя его с помощью @Inject.
  3. Используете navigationHelper для отображения NewsListFragment.

Чтобы протестировать UI у MainActivity, вам необходимо использовать NavigationHelper для отображения NewsListFragment при запуске MainActivity. Для этого вам потребуется создать тестовый класс, что вы и сделаете дальше.

Реализация RoboMainActivityTest

Сейчас вы создадите тестовый класс и назовете его RoboMainActivityTest.

Откройте ui/MainActivity.kt. Теперь поместите курсор на имя класса и нажмите Option-Enter. Вы увидите следующий результат:

Создание Unit-теста

Выберите Create test и нажмите Enter, чтобы получить следующее диалоговое окно:

Настройка теста

Выберите JUnit4 в качестве библиотеки тестирования. Важно отметить, что вам нужно выбрать test в качестве назначения типа сборки:

Папка Robolectric UI Tests.

Сначала вы получите пустой класс:

class RoboMainActivityTest

Важно убедиться, что этот файл входит в число исходных файлов для тестового типа сборки:

Расположение RoboMainActivityTest.

Далее вы добавите код для реализации теста.

 

Создание Класса-скелета для Теста UI Robolectric

Откройте только что созданный RoboMainActivityTest.kt и замените его содержимое следующим:

import dagger.hilt.android.testing.HiltAndroidRule
import dagger.hilt.android.testing.HiltAndroidTest
import dagger.hilt.android.testing.HiltTestApplication
import org.junit.Assert.assertTrue
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import org.robolectric.annotation.LooperMode

@HiltAndroidTest // 1
@Config(application = HiltTestApplication::class) // 2
@RunWith(RobolectricTestRunner::class) // 3
@LooperMode(LooperMode.Mode.PAUSED)  // 4
class RoboMainActivityTest {

    @get:Rule
    var hiltAndroidRule = HiltAndroidRule(this) // 5

    @Before
    fun setUp() {
        hiltAndroidRule.inject() // 6
    }

    @Test
    fun whenMainActivityLaunchedNavigationHelperIsInvokedForFragment() { // 7
        assertTrue(true)
    }
}

Здесь много важных вещей, которые стоит отметить:

1. Вы аннотируете тестовый класс с помощью @HiltAndroidTest. Это очень важно, потому что это говорит Hilt, что вам нужно создать график зависимостей для теста, отличный от того, который вы используете в приложении.

2. Как вы уже узнали из предыдущих туториалов, реализация Application для приложения - это место, где начинается построение графика зависимостей. Здесь вы используете @HiltAndroidApp, чтобы сообщить Hilt, что это за реализация, точно так же, как вы делали это в RwNewsApplication.kt. Но, как вы только что узнали, для тестов требуется другой график зависимостей с разными объектами, которые вы создаете с помощью другой реализации Application.

Hilt уже предоставляет это в HiltTestApplication. Используя @Config, вы сообщаете Hilt, что реализация Application, используемая для тестов, - это HiltTestApplication. Как вы увидите позже, тот же результат вы можете получить с помощью конфигурации в файле robolectric.properties.

3. Используя @RunWith, вы явно определяете RobolectricTestRunner как TestRunner, который будет использоваться для запуска тестов в этом файле.

4. Распределение (потоков) во время выполнения теста - сложная тема, для подробного объяснения которой потребуется свой собственный туториал. Здесь же вы используете @LooperMode(LooperMode.Mode.PAUSED) для запуска Robolectric-тестов в основном потоке.

5. Для создания и удаления графика зависимостей, предоставляемого Hilt для каждого выполнения теста, вы создаете экземпляр HiltAndroidRule в hiltAndroidRule.

6. Вы вызываете inject() в hiltAndroidRule в начале каждого теста. Как вы увидите позже, с помощью этого объекты из графика зависимостей теста Hilt вводятся в сам тест.

7. Конечно, вам нужно создать тестовую функцию с именем, которое объясняет, что вы на самом деле тестируете. В этом случае вы просто утверждаете что-то верно, чтобы проверить, что все конфигурации правильные.

Теперь вы готовы запустить тест, нажав на зеленую стрелку:

Запуск RoboMainActivityTest.

Это дает вам следующий результат:

Результат теста RoboMainActivity

Далее вы узнаете, как настроить тесты Robolectric с помощью файла robolectric.properties.

 

Настройка Robolectric с Помощью robolectric.properties

Чтобы настроить версию SDK для использования при запуске тестов Robolectric,

Правой кнопкой мыши нажмите на директорию test и выберите New > Directory


Затем выберите resources


2. Жмем правой кнопкой мыши по директории resources и выбираем New > File.

Затем создайте новый файл с названием robolectric.properties

У вас будет файл внутри папки resources для тестового типа сборки, как здесь:

Robolectric Properties File

Теперь добавьте следующие свойства:

sdk=28
application=dagger.hilt.android.testing.HiltTestApplication

Здесь вы используете sdk, чтобы выбрать версию SDK для тестов Robolectric. Вы используете application, чтобы определить Application реализацию, которая будет использоваться для тестов.

Теперь попробуйте разные версии SDK и посмотрите, как меняются результаты тестирования.

Реализация Robolectric UI Test

В предыдущей части для проверки конфигурации Hilt для Robolectric вы создали пустой тест. Теперь настало время создать реальный.

Для этого вам нужно:

  • Настроить ActivityScenario API для запуска MainActivity.
  • Замените существующую NavigationHelper реализацию поддельной.
  • Реализуйте утверждения, сообщающие, успешен ли тест или нет.

Далее пишем код для реализации Robolectric UI теста.

Настройка ActivityScenario

Для настройки ActivityScenario API, откройте RoboMainActivityTest.kt и замените существующий код класса этим:

import androidx.test.ext.junit.rules.ActivityScenarioRule

@HiltAndroidTest
@Config(application = HiltTestApplication::class)
@RunWith(RobolectricTestRunner::class)
@LooperMode(LooperMode.Mode.PAUSED) 
class RoboMainActivityTest {

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

  @get:Rule(order = 1) // 2
  var activityScenarioRule: ActivityScenarioRule =
      ActivityScenarioRule(MainActivity::class.java) // 1

  @Before
  fun setUp() {
    hiltAndroidRule.inject()
  }

  @Test
  fun whenMainActivityLaunchedNavigatorIsInvokedForFragment() {
    activityScenarioRule.scenario // 3
  }
}

Activity Scenario API позволяет запускать Activity во время теста. Для его работы вам нужно:

  1. Инициализируйте новое JUnit правило типа ActivityScenarioRule в activityScenarioRule.
  2. Помните, что JUnit правило- это умный способ создать и инициализировать среду выполнения теста и выпустить ее по завершении теста. Если у вас есть несколько правил, то важен порядок их выполнения. Здесь вы используете order, чтобы обеспечить выполнение правил в правильной последовательности. HiltAndroidRule должен быть первым запускаемым правилом, для этого вы устанавливаете order = 0. Заметьте, JUnit добавил этот атрибут в версии 4.13.1.
  3. Чтобы запустить MainActivity, вы просто получаете доступ к scenario в activityScenarioRule. Обратите внимание, что MainActivity - это тот параметр, который вы установили как значение типа параметра в ActivityScenarioRule .

Теперь все выглядит правильно, но при обычном запуске теста в Android Studio возникает следующая ошибка:

kotlin.UninitializedPropertyAccessException: lateinit property navigationHelper has not been initialized
  at com.raywenderlich.rwnews.ui.MainActivity.onCreate(MainActivity.kt:57)

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

./gradlew testDebugUnitTest --tests "*.RoboMainActivityTest.*"

Заметка

Чтобы запустить тест из командной строки, вам необходимо убедиться, что вы используете Java 8. На данный момент это не работает с более поздней версией Java.

Теперь тест должен пройти успешно, подтверждая, что вы правильно настроили UI тест с Robolectric и Hilt. Ваш следующий шаг - реализовать реальный тест.

Замена NavigationHelper Подделкой

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

  • @UninstallModules
  • @BindValue

Как вы знаете, @Module сообщает Dagger, как предоставить экземпляр для определенного типа. Если вы откроете ActivityModule, то увидите следующее определение для NavigationHelper:

@Module
@InstallIn(ActivityComponent::class) // HERE
interface ActivityModule {

  @Binds
  fun provideNavigationHelper(
    impl: NavigationHelperImpl
  ): NavigationHelper
}

Как видите, @InstallIn использовался для установки привязок в ActivityModule компонента для определенной области. В данном случае это ActivityComponent.

Чтобы заменить привязку для NavigationHelper, вы должны иметь возможность удалить (деинсталлировать) ее из того же компонента. Для этого вы будете использовать @UninstallModules.

Откройте RoboMainActivityTest.kt и добавьте следующее определение (добавляя недостающий импорт с помощью IDE):

@HiltAndroidTest
@Config(application = HiltTestApplication::class)
@RunWith(RobolectricTestRunner::class)
@LooperMode(LooperMode.Mode.PAUSED)
@UninstallModules(ActivityModule::class) // HERE
class RoboMainActivityTest {
  // ...
}

С помощью этой простой строки кода вы удаляете привязки, определенные в ActivityModule.
Конечно, вам нужно предоставить альтернативную привязку для NavigationHelper.

Создайте новый файл с именем FakeNavigationHelper.kt в папке testShared> kotlin> fakes. Затем добавьте в файл следующий код:

import androidx.fragment.app.Fragment
import com.raywenderlich.rwnews.ui.navigation.NavigationHelper

class FakeNavigationHelper : NavigationHelper {

  data class NavigationInput(
      val anchorId: Int,
      val fragment: Fragment,
      val backStack: String?
  )

  val replaceRequests = mutableListOf()

  override fun replace(anchorId: Int, fragment: Fragment, backStack: String?) {
    replaceRequests.add(NavigationInput(anchorId, fragment, backStack))
  }
}

Это простая реализация поддельного NavigationHelper. Чтобы добавить его в график зависимостей для RoboMainActivityTest, вам просто нужно добавить следующее объявление в класс (добавляя недостающий импорт с помощью IDE):

class RoboMainActivityTest {
  // ...
  @BindValue // 1
  @JvmField // 2
  val navigator: NavigationHelper = FakeNavigationHelper()  // 3
  // ...
}

С помощью этого кода вы:

  1. Используете @BindValue, чтобы добавить привязку для FakeNavigationHelper к графику зависимостей для теста.
  2. Используете @JvmField, чтобы попросить Kotlin сгенерировать навигатор как поле без геттеров и сеттеров. Это аннотация Kotlin.
  3. Определяете поле navigation, которое будет содержать экземпляр привязки для NavigationHelper. На практике это имеет такой же эффект, как и использование @Inject в обычной точке входа.

Важно отметить, что, пока @UninstallModules удаляет все привязки в модуле, который вы передаете как значение атрибута, @BindValue добавляет одну привязку. Позже вы увидите, как заменить весь @Module.

 

Добавление Утверждений для UI теста

Теперь вы, наконец, готовы реализовать RoboMainActivityTest с реальным тестом. Откройте RoboMainActivityTest.kt и замените тестовую функцию whenMainActivityLaunchedNavigationHelperIsInvokedForFragment внутри класса RoboMainActivityTest следующим новым определением функции:

import com.google.common.truth.Truth.assertThat
import com.raywenderlich.rwnews.ui.list.NewsListFragment
import com.raywenderlich.rwnews.R

// Annotations
class RoboMainActivityTest {
  // ...
  @Test
  fun whenMainActivityLaunchedNavigationHelperIsInvokedForFragment() {
    activityScenarioRule.scenario // 1
    val fakeHelper = navigator as FakeNavigationHelper // 2
    with(fakeHelper.replaceRequests[0]) { // 3
      assertThat(anchorId)
          .isEqualTo(R.id.anchor)
      assertThat(fragment)
          .isInstanceOf(NewsListFragment::class.java)
      assertThat(backStack)
          .isNull()
    }
  }
  // ...
}

В этом коде вы:

  1. Запускаете MainActivity, получив доступ к свойству scenario из activityScenarioRule.
  2. Доступ к navigator после преобразования в FakeNavigationHelper.
  3. Используете библиотеку Truth, чтобы убедиться, что FakeNavigationHelper был вызван с ожидаемыми значениями параметров.

Заметка

Вы также можете использовать любую другую библиотеку утверждений, которую предпочтете.

Теперь вы, наконец, можете запустить тест и проверить его результат.

Поздравляю! Вы создали свой первый UI тест с помощью Hilt и Robolectric. Попутно вы узнали, как использовать @UninstallModules и @BindValue, чтобы адаптировать график зависимостей приложения к определенному тесту.

Реализация Аппаратных тестов с Hilt и Espresso

В предыдущей части туториала вы создали UI тест, используя Robolectric. Теперь вы воспользуетесь Hilt и Espresso, чтобы выполнить чуть более сложный тест. В частности, вы создадите Espresso UI тест для NewsListFragment. В данном случае у вас немного больше работы, потому что вам необходимо:

  • Добавить зависимости Hilt для инструментального теста.
  • Создать действие @AndroidEntryPoint для использования в качестве контейнера для тестируемого Fragment.
  • Реализовать служебный класс для запуска тестируемого Fragment в Activity, включенным Hilt.
  • Создать собственный AndroidJUnitRunner, который использует HiltTestApplication, вместо того, который Android предоставляет по умолчанию, а затем настроить его в build.gradle.
  • Реализовать и запустить аппаратный тест.

Пишите код и все будет хорошо. :]

Заметка

Как упоминалось в начале туториала, не беспокойтесь, если FragmentTestUtil.kt в типе сборки androidTest не компилируется сразу. Очень скоро вы это исправите.

Добавление Зависимостей в Аппаратный (или Инструментальный) Тест

Чтобы запустить UI тест с помощью Espresso и Hilt вам нужно добавить следующие зависимости в build.gradle в приложении:

// ...
dependencies {
  // Hilt for instrumented tests.
  androidTestImplementation "com.google.dagger:hilt-android-testing:$hilt_android_version" // 1
  kaptAndroidTest "com.google.dagger:hilt-android-compiler:$hilt_android_version" // 2
  // ...
}

Нужно отметить всего лишь две вещи:

  1. Вы добавляете зависимость в библиотеку Hilt для инструментального теста в тип сборки androidTest.
  2. По той же причине вы используете kaptAndroidTest для установки процессора аннотаций, ответственного за создание кода тестирования из определения Hilt в инструментальных тестах.

Теперь Синхронизируйте Проект с файлами Gradle, и вы готовы начать писать ваш Espresso тест. Его зависимости уже в проекте.

Создание Контейнера Actitvity для Тестируемого Фрагмента

Обычно, до того, как вы тестируете Fragment, вы первым запускаете Activity как его контейнер. С Hilt возникает проблема: если Fragment является @AndroidEntryPoint, то же самое должно быть верно и для Activity контейнера. Если вы просто используете ActivityScenario, это не произойдет автоматически. Вот почему вам нужно создать контейнер activity.

Начните с создания новой папки с именем kotlin в debug, затем создайте внутри пакет с именем com.raywenderlich.rwnews. Следующим шагом создайте в нем новый класс с именем HiltActivityForTest.kt и добавьте следующий код:

import androidx.appcompat.app.AppCompatActivity
import dagger.hilt.android.AndroidEntryPoint

@AndroidEntryPoint // HERE
class HiltActivityForTest : AppCompatActivity()

Здесь вы создаете HiltActivityForTest только для того, чтобы иметь возможность пометить его с помощью @AndroidEntryPoint, как объяснялось ранее.

Затем вам нужно указать инструментальной среде использовать HiltActivityForTest во время теста. Создайте AndroidManifest.xml для отладки, как здесь:



  
    
  

В этом месте ваша структура будет выглядеть, как на изображении ниже, и FragmentTestUtil.kt будет успешно построен. Ага! :]

Но что именно внутри FragmentTestUtil? Вы узнаете дальше.

Запуск Тестируемого Фрагмента

Стартовый проект для RW News уже содержит FragmentTestUtil.kt. Откройте его и посмотрите на его код, особенно на следующую подпись:

inline fun  launchFragmentInHiltContainer(
    fragmentArgs: Bundle? = null,
    @StyleRes themeResId: Int = R.style.FragmentScenarioEmptyFragmentActivityTheme,
    crossinline action: Fragment.() -> Unit = {}
) {
  // ...
}

Вы будете использовать launchFragmentInHiltContainer() для запуска Fragment в контексте HiltActivityForTest. Оба они Hilt @AndroidEntryPoints.

Создание Пользовательского AndroidJUnitRunner

Чтобы запустить UI тест, который вы реализовали с помощью Robolectric, вам нужно было указать, какую реализацию TestRunner использовать. Вы делали это используя или @Config в самом тесте, или application в robolectric.properties.

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

Создайте новый пакет runner в androidTest/java/com.raywenderlich.rwnews. После этого создайте внутри него HiltTestRunner.kt и добавьте следующий код:

import android.app.Application
import android.content.Context
import androidx.test.runner.AndroidJUnitRunner
import dagger.hilt.android.testing.HiltTestApplication

class HiltTestRunner : AndroidJUnitRunner() {

    override fun newApplication(
        cl: ClassLoader?,
        className: String?,
        context: Context?
    ): Application {
        return super.newApplication(
            cl,
            HiltTestApplication::class.java.name, // HERE
            context
        )
    }
}

Здесь вы переопределяете newApplication и заставляете Hilt использовать HiltTestApplication в качестве реализации Application для тестов. Этот код указывает Hilt использовать другой график зависимостей для ваших аппаратных тестов.

Следующим шагом, вам нужно указать Gradle использовать эту реализацию вместо существующей. Откройте build.gradle для приложения и примените следующее изменение:

// ...
android {
  ...
  defaultConfig {
    ...
    testInstrumentationRunner "com.raywenderlich.rwnews.runner.HiltTestRunner" // HERE
  }
  // ...
}
// ...

Здесь вы заменили существующее значение testInstrumentationRunner на полностью квалифицированное имя HiltTestRunner.

Далее Синхронизируйте Проект с файлами Gradle. После успешного завершения, самое время написать инструментальный тест для NewsListFragment.

Реализация NewsListFragmentTest

Чтобы реализовать NewsListFragment тест, создайте новый файл с именем NewsListFragmentTest.kt внутри androidTest/java/com.raywenderlich.rwnews и добавьте следующий код (добавляя недостающий импорт с помощью IDE):

@HiltAndroidTest
@UninstallModules(AppModule::class) // HERE
class NewsListFragmentTest {

    @get:Rule
    var hiltAndroidRule = HiltAndroidRule(this)

    @Before
    fun setUp() {
        hiltAndroidRule.inject()
    }
}

В этом блоке кода аннотация @UninstallModules используется для удаления привязок, определенных через класс AppModule, который содержит привязки для NewsRepository и RwNewsLogger.

Далее в том же файле класса добавьте следующий код (добавляя недостающий импорт с помощью IDE):

class NewsListFragmentTest {

    // Rule definition
    // Before tests setup

    @Module
    @InstallIn(SingletonComponent::class) // 1
    object TestAppModule {

        @Provides
        fun provideNewsRepository(): NewsRepository { // 2
            return FakeNewsRepository().apply {
                insert(News(1, "First Title", "First Body"))
                insert(News(2, "Second Title", "Second Body"))
                insert(News(3, "Third Title", "Third Body"))
            }
        }

        @Provides
        fun provideNewsLogger(): RwNewsLogger = FakeNewsLogger() // 2
    }
}

 

В этом блоке кода:

  1. Устанавливаем TestAppModule, чтобы заменить предыдущие привязки для теста.
  2. Вы устанавливаете локальный модуль @Module для теста. Он предоставляет экземпляр FakeNewsRepository, который вы заполняете фиктивными данными. То же самое делаем и с FakeNewsLogger.

Наконец, в том же файле класса добавьте тестовый код (добавляя недостающий импорт с помощью IDE):


class NewsListFragmentTest {

  // Rule definition
  // Before tests setup
  // Module setup

    @Test
    fun whenDisplayed_newsListFromRepoIsDisplayed() { // 1
        launchFragmentInHiltContainer() // 2
        scrollAtAndCheckTestVisible(0, "First Title")
        scrollAtAndCheckTestVisible(1, "Second Title")
        scrollAtAndCheckTestVisible(2, "Third Title")
    }

    fun scrollAtAndCheckTestVisible(position: Int, text: String) {
        onView(ViewMatchers.withId(R.id.recycler_view))
            .perform(RecyclerViewActions
                .scrollToPosition(position))
        onView(withText(text)).check(matches(isDisplayed()))
    }
}

Теперь этот тест должен быть достаточно простым, но есть пара моментов, на которые следует обратить внимание:

  1. Реализуйте тест, утверждая, что данные из NewsRepository фактически отображаются в RecyclerView в NewsListFragment.
  2. Используйте launchFragmentInHiltContainer() для запуска NewsListFragment в HiltActivityForTest, подготовленный ранее.

Теперь вы можете просто запустить тест как обычно. При успешном его прохождении вы получите что-то вроде этого:

Реализация Тестов, Помогающих Структурировать Код

В предыдущем тесте вы могли просто заменить привязку для NewsRepository вместо всего AppModule. Вы сделали это таким способом для того, чтобы увидеть, как заменить @Module; но это доказывает, насколько важно группировать привязки вместе в зависимости от того, как вы можете изменить или заменить их в тесте.

Даже если NewsRepository и RwNewsLogger имеют одинаковую область видимости, вы все равно должны определять их в разных @Modules.

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

Вы также встретили и использовали @UninstallModules и @BindValue.

Оригинал статьи

Содержание