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