دليل اختبار Hilt

من مزايا استخدام أُطر عمل حقن التبعية مثل 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")

}

إعداد اختبار واجهة المستخدم

يجب إضافة تعليق توضيحي إلى أي اختبار لواجهة المستخدم يستخدم 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

بدلاً من ذلك، يمكنك ضبط التطبيق في كل اختبار على حدة باستخدام تعليق Robolectric التوضيحي @Config:

@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 بـ fake باستخدام @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 التي توسّع التطبيق الذي تم تمريره كمَعلمة.

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

في المثال، تنشئ Hilt فئة Application باسم HiltTestApplication_Application تمدّد فئة BaseApplication. بشكل عام، يكون اسم التطبيق الذي تم إنشاؤه هو اسم الفئة التي تم التعليق عليها والمُلحقة بـ _Application. يجب ضبط تطبيق اختبار Hilt الذي تم إنشاؤه ليتم تشغيله في اختبارات قياس حالة التطبيق أو اختبارات Robolectric كما هو موضّح في تطبيق الاختبار.

كائنات TestRule متعددة في اختبار لقياس حالة التطبيق

تجمع اختبارات واجهة مستخدم Compose بين 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.
}

استخدام نقطة دخول قبل توفّر مكوّن singleton

يوفّر التعليق التوضيحي @EarlyEntryPoint مخرجًا عندما يلزم إنشاء نقطة دخول Hilt قبل أن يصبح مكوّن singleton متاحًا في اختبار Hilt.

يمكنك الاطّلاع على مزيد من المعلومات حول @EarlyEntryPoint في مستندات Hilt.

مراجع إضافية

لمعرفة المزيد عن الاختبار، راجِع المراجع الإضافية التالية:

الوثائق

محتوى المشاهدات