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

هناك عدة كائنات 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 من مستودع architecture-samples GitHub بدلاً من ذلك.

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

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

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