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


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

Kotlin

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


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

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

عليك وضع التعليق التوضيحي @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 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.
}

إذا كنت تستخدم إصدارًا من "مكوّن إضافي لنظام Gradle المتوافق مع Android" أقل من 4.2، فعِّل تحويل فئات @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.
}

launchFragmentInContainer

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

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

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

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

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