دليل اختبار 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 في مشروعك:

رائع

dependencies {
    // For Robolectric tests.
    testImplementation 'com.google.dagger:hilt-android-testing:2.44'
    // ...with Kotlin.
    kaptTest 'com.google.dagger:hilt-android-compiler:2.44'
    // ...with Java.
    testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.44'


    // For instrumented tests.
    androidTestImplementation 'com.google.dagger:hilt-android-testing:2.44'
    // ...with Kotlin.
    kaptAndroidTest 'com.google.dagger:hilt-android-compiler:2.44'
    // ...with Java.
    androidTestAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.44'
}

Kotlin

dependencies {
    // For Robolectric tests.
    testImplementation("com.google.dagger:hilt-android-testing:2.44")
    // ...with Kotlin.
    kaptTest("com.google.dagger:hilt-android-compiler:2.44")
    // ...with Java.
    testAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.44")


    // For instrumented tests.
    androidTestImplementation("com.google.dagger:hilt-android-testing:2.44")
    // ...with Kotlin.
    kaptAndroidTest("com.google.dagger:hilt-android-compiler:2.44")
    // ...with Java.
    androidTestAnnotationProcessor("com.google.dagger:hilt-android-compiler:2.44")
}

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

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

رائع

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

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

رائع

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

تشغيلFragmentInContainer

لا يمكن استخدام "launchFragmentInContainer" من مكتبة "androidx.fragment:fragment-testing" مع Hilt، لأنّها تعتمد على نشاط لم تتم إضافة تعليقات توضيحية إليه باستخدام @AndroidEntryPoint.

استخدِم رمز launchFragmentInHiltContainer من مستودع GitHub architecture-samples بدلاً من ذلك.

استخدِم نقطة دخول قبل توفُّر مكوّن سينغلتون

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

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