دليل اختبار Hilt

من مزايا استخدام أُطر عمل لتوفير التبعية مثل Hilt أنّها تسهّل اختبار الرمز البرمجي.

اختبارات الوحدات

لا حاجة إلى استخدام Hilt في اختبارات الوحدات، لأنّه عند اختبار فئة تستخدم ميزة إدخال البيانات في الدالة الإنشائية، لن تحتاج إلى استخدام Hilt لإنشاء مثيل لهذه الفئة. بدلاً من ذلك، يمكنك استدعاء أداة إنشاء الفئة مباشرةً من خلال تمرير تبعيات وهمية أو محاكاة، تمامًا كما تفعل إذا لم يتم وضع تعليق توضيحي على أداة الإنشاء:

Kotlin

@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 في مشروعك:

Groovy

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

Kotlin

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

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

يجب إضافة التعليق التوضيحي @HiltAndroidTest إلى أي اختبار لواجهة المستخدم يستخدِم Hilt. هذه التعليق التوضيحي مسؤول عن إنشاء مكوّنات Hilt لكل اختبار.

عليك أيضًا إضافة HiltAndroidRule إلى فئة الاختبار. يدير هذا الصف حالة المكوّنات ويُستخدم لتنفيذ عملية الإدخال في الاختبار:

Kotlin

@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 الذي تم إنشاؤه.

Kotlin

// 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 كما هو موضّح في دليل اختبارات الوحدات المبرمَجة. تأكَّد من استخدام مسار الفئة الكامل:

Groovy

android {
    defaultConfig {
        // Replace com.example.android.dagger with your class path.
        testInstrumentationRunner "com.example.android.dagger.CustomTestRunner"
    }
}

Kotlin

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:

Kotlin

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

إذا كنت تستخدم إصدارًا من "إضافة Gradle لنظام التشغيل Android" أقل من 4.2، فعِّل تحويل فئات @AndroidEntryPoint في اختبارات الوحدات المحلية من خلال تطبيق الإعداد التالي في ملف build.gradle الخاص بالوحدة:

Groovy

hilt {
    enableTransformForLocalTests = true
}

Kotlin

hilt {
    enableTransformForLocalTests = true
}

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

ميزات الاختبار

بعد أن يصبح Hilt جاهزًا للاستخدام في الاختبارات، يمكنك استخدام العديد من الميزات لتخصيص عملية الاختبار.

إدخال أنواع في الاختبارات

لإدخال أنواع في اختبار، استخدِم @Inject لإدخال الحقول. لإخبار Hilt بملء الحقول @Inject، استخدِم الدالة hiltRule.inject().

في ما يلي مثال على اختبار مزوّد بأدوات:

Kotlin

@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 على النحو التالي:

Kotlin

@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. بدلاً من ذلك، يتم إدخال جميع الاختبارات في هذا المجلد مع التبعية الوهمية.

Kotlin

@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 في فئة الاختبار:

Kotlin

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

Java

@UninstallModules(AnalyticsModule.class)
@HiltAndroidTest
public final class SettingsActivityTest { ... }

بعد ذلك، عليك استبدال عملية الربط. أنشئ وحدة جديدة ضمن فئة الاختبار تحدّد عملية الربط بالاختبار:

Kotlin

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

Kotlin

@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 على النحو التالي:

Kotlin

...
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، وتوسّع هذه الفئة التطبيق الذي تم تمريره كمَعلمة.

Kotlin

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

Java

@CustomTestApplication(BaseApplication.class)
interface HiltTestApplication { }

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

عناصر TestRule متعدّدة في اختبارك المبرمَج

إذا كان لديك عناصر TestRule أخرى في اختبارك، هناك عدة طرق للتأكّد من أنّ جميع القواعد تعمل معًا.

يمكنك تجميع القواعد معًا على النحو التالي:

Kotlin

@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. لا تعمل هذه الميزة إلا في الإصدار 4.13 من JUnit أو الإصدارات الأحدث:

Kotlin

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