دليل اختبار 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")

}

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

عليك وضع التعليق التوضيحي @HiltAndroidTest على أي اختبار لواجهة المستخدم يستخدم Hilt. هذا التعليق التوضيحي مسؤول عن إنشاء مكوّنات 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 لاستخدامها في الاختبارات. إذا كانت اختباراتك بحاجة إلى تطبيق أساسي مختلف، يُرجى الاطّلاع على مقالة تطبيق مخصّص للاختبارات.

عليك ضبط تطبيق الاختبار ليتم تشغيله في اختباراتك التي تم تتبّعها instrumented tests أو اختبارات Robolectric tests. لا تنطبق التعليمات التالية على 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
}

بما أنّ الدوال البرمجية القابلة للإنشاء تستهلك عادةً هذه التبعيات بشكل غير مباشر من خلال a 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 توسّع التطبيق الذي مرّرته كمعلَمة.

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

في المثال، ينشئ Hilt تطبيقًا باسم HiltTestApplication_Application يوسّع فئة BaseApplication.Application بشكل عام، يكون اسم التطبيق الذي تم إنشاؤه هو اسم الفئة التي تم وضع التعليق التوضيحي عليها متبوعًا بـ _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.
}

استخدام نقطة دخول قبل توفّر مكوّن العنصر الأحادي

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

مزيد من المعلومات عن @EarlyEntryPoint في مستندات Hilt.

مراجع إضافية

لمعرفة المزيد عن الاختبار، يُرجى الاطّلاع على المَراجع الإضافية التالية:

الوثائق

محتوى "العروض"