دليل اختبار 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.51.1'
    // ...with Kotlin.
    kaptTest 'com.google.dagger:hilt-android-compiler:2.51.1'
    // ...with Java.
    testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.51.1'


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

Kotlin

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


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

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

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

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

استبدال رابط

إذا كنت بحاجة إلى إدخال مثيل مزيّف أو وهمي لمكوّن تابع، عليك إخبار IDE 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 لربط الحقول في اختبارك بسهولة في مخطّط @BindValueالتسلسل الهرمي للتبعيات في Hilt. أضِف تعليقًا توضيحيًا إلى حقل باستخدام @BindValue، وسيتم ربطه ضمن نوع الحقل المُعلَن باستخدام أيّ محددات متوفّرة لذلك الحقل.

في مثال AnalyticsService، يمكنك استبدال AnalyticsService بعبارة fake باستخدام @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. بشكلٍ عام، اسم التطبيق الذي تم إنشاؤه هو اسم ال annotated class المُرفَق بـ _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 من مستودع GitHub architecture-samples بدلاً من ذلك.

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

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

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