راهنمای تست هیلت

یکی از مزایای استفاده از چارچوب‌های تزریق وابستگی مانند Hilt این است که تست کد شما را آسان‌تر می‌کند.

تست‌های واحد

Hilt برای تست‌های واحد ضروری نیست، زیرا هنگام تست کلاسی که از تزریق سازنده استفاده می‌کند، نیازی به استفاده از Hilt برای نمونه‌سازی آن کلاس ندارید. در عوض، می‌توانید مستقیماً با ارسال وابستگی‌های جعلی یا ساختگی، سازنده کلاس را فراخوانی کنید، درست همانطور که اگر سازنده حاشیه‌نویسی نشده باشد، این کار را انجام می‌دهید:

کاتلین

@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(...)
  }
}

جاوا

@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'
}

کاتلین

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")
}

تنظیمات تست رابط کاربری

شما باید هر تست رابط کاربری که از Hilt استفاده می‌کند را با @HiltAndroidTest حاشیه‌نویسی کنید. این حاشیه‌نویسی مسئول تولید کامپوننت‌های Hilt برای هر تست است.

همچنین، باید HiltAndroidRule را به کلاس تست اضافه کنید. این کلاس وضعیت کامپوننت‌ها را مدیریت می‌کند و برای انجام تزریق در تست شما استفاده می‌شود:

کاتلین

@HiltAndroidTest
class SettingsActivityTest {

  @get:Rule
  var hiltRule = HiltAndroidRule(this)

  // UI tests here.
}

جاوا

@HiltAndroidTest
public final class SettingsActivityTest {

  @Rule
  public HiltAndroidRule hiltRule = new HiltAndroidRule(this);

  // UI tests here.
}

در مرحله بعد، تست شما باید از کلاس Application که Hilt به طور خودکار برای شما تولید می‌کند، مطلع باشد.

برنامه تست

شما باید تست‌های ابزاری که از Hilt استفاده می‌کنند را در یک شیء Application که از Hilt پشتیبانی می‌کند، اجرا کنید. این کتابخانه HiltTestApplication برای استفاده در تست‌ها ارائه می‌دهد. اگر تست‌های شما به یک برنامه پایه متفاوت نیاز دارند، به Custom application for tests مراجعه کنید.

شما باید برنامه آزمایشی خود را طوری تنظیم کنید که در تست‌های ابزار دقیق یا تست‌های Robolectric اجرا شود. دستورالعمل‌های زیر مختص Hilt نیستند، اما دستورالعمل‌های کلی در مورد نحوه مشخص کردن یک برنامه سفارشی برای اجرا در تست‌ها هستند.

تنظیم برنامه تست در تست‌های ابزاری

برای استفاده از برنامه تست Hilt در تست‌های ابزار دقیق ، باید یک اجراکننده تست جدید پیکربندی کنید. این کار باعث می‌شود Hilt برای تمام تست‌های ابزار دقیق در پروژه شما کار کند. مراحل زیر را انجام دهید:

  1. یک کلاس سفارشی ایجاد کنید که AndroidJUnitRunner در پوشه androidTest ارث‌بری کند.
  2. تابع newApplication را بازنویسی کنید و نام برنامه آزمایشی Hilt تولید شده را به آن بدهید.

کاتلین

// 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)
    }
}

جاوا

// 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 خود، همانطور که در راهنمای تست واحد instrumented توضیح داده شده است، پیکربندی کنید. مطمئن شوید که از classpath کامل استفاده می‌کنید:

گرووی

android {
    defaultConfig {
        // Replace com.example.android.dagger with your class path.
        testInstrumentationRunner "com.example.android.dagger.CustomTestRunner"
    }
}

کاتلین

android {
    defaultConfig {
        // Replace com.example.android.dagger with your class path.
        testInstrumentationRunner = "com.example.android.dagger.CustomTestRunner"
    }
}
تنظیم برنامه تست در تست‌های Roboelectric

اگر از Robolectric برای تست لایه رابط کاربری خود استفاده می‌کنید، می‌توانید در فایل robolectric.properties مشخص کنید که از کدام برنامه استفاده کنید:

application = dagger.hilt.android.testing.HiltTestApplication

به عنوان یک روش جایگزین، می‌توانید برنامه را در هر تست به صورت جداگانه با استفاده از حاشیه‌نویسی @Config در Robolectric پیکربندی کنید:

کاتلین

@HiltAndroidTest
@Config(application = HiltTestApplication::class)
class SettingsActivityTest {

  @get:Rule
  var hiltRule = HiltAndroidRule(this)

  // Robolectric tests here.
}

جاوا

@HiltAndroidTest
@Config(application = HiltTestApplication.class)
class SettingsActivityTest {

  @Rule public HiltAndroidRule hiltRule = new HiltAndroidRule(this);

  // Robolectric tests here.
}

اگر از نسخه پایین‌تر از ۴.۲ افزونه اندروید Gradle استفاده می‌کنید، با اعمال پیکربندی زیر در فایل build.gradle ماژول خود، تبدیل کلاس‌های @AndroidEntryPoint را در تست‌های واحد محلی فعال کنید:

گرووی

hilt {
    enableTransformForLocalTests = true
}

کاتلین

hilt {
    enableTransformForLocalTests = true
}

اطلاعات بیشتر در مورد enableTransformForLocalTests در مستندات Hilt موجود است.

ویژگی‌های تست

زمانی که Hilt آماده استفاده در تست‌های شما شد، می‌توانید از چندین ویژگی برای سفارشی‌سازی فرآیند تست استفاده کنید.

تزریق انواع در تست‌ها

برای تزریق انواع به یک تست، @Inject برای تزریق فیلد استفاده کنید. برای اینکه به Hilt بگویید فیلدهای @Inject را پر کند، hiltRule.inject() را فراخوانی کنید.

به مثال زیر از یک آزمایش ابزار دقیق توجه کنید:

کاتلین

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

جاوا

@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 بگویید که از اتصالی که در کد تولید استفاده کرده استفاده نکند و به جای آن از اتصال دیگری استفاده کند. برای جایگزینی یک اتصال، باید ماژولی را که شامل اتصال است با یک ماژول آزمایشی که شامل اتصالاتی است که می‌خواهید در تست استفاده کنید، جایگزین کنید.

برای مثال، فرض کنید کد عملیاتی شما یک binding برای AnalyticsService به صورت زیر تعریف می‌کند:

کاتلین

@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

جاوا

@Module
@InstallIn(SingletonComponent.class)
public abstract class AnalyticsModule {

  @Singleton
  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    AnalyticsServiceImpl analyticsServiceImpl
  );
}

برای جایگزینی اتصال AnalyticsService در تست‌ها، یک ماژول Hilt جدید در پوشه test یا androidTest با وابستگی جعلی ایجاد کنید و آن را با @TestInstallIn حاشیه‌نویسی کنید. در عوض، تمام تست‌های موجود در آن پوشه با وابستگی جعلی تزریق می‌شوند.

کاتلین

@Module
@TestInstallIn(
    components = [SingletonComponent::class],
    replaces = [AnalyticsModule::class]
)
abstract class FakeAnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    fakeAnalyticsService: FakeAnalyticsService
  ): AnalyticsService
}

جاوا

@Module
@TestInstallIn(
    components = SingletonComponent.class,
    replaces = AnalyticsModule.class
)
public abstract class FakeAnalyticsModule {

  @Singleton
  @Binds
  public abstract AnalyticsService bindAnalyticsService(
    FakeAnalyticsService fakeAnalyticsService
  );
}

جایگزینی یک متغیر در یک تست واحد

برای جایگزینی یک متغیر در یک تست واحد به جای تمام تست‌ها، یک ماژول Hilt را با استفاده از حاشیه‌نویسی @UninstallModules از تست حذف نصب کنید و یک ماژول تست جدید درون تست ایجاد کنید.

با پیروی از مثال AnalyticsService از نسخه قبلی، با استفاده از حاشیه‌نویسی @UninstallModules در کلاس تست، به Hilt بگویید که ماژول عملیاتی را نادیده بگیرد:

کاتلین

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsActivityTest { ... }

جاوا

@UninstallModules(AnalyticsModule.class)
@HiltAndroidTest
public final class SettingsActivityTest { ... }

در مرحله بعد، باید اتصال را جایگزین کنید. یک ماژول جدید در کلاس تست ایجاد کنید که اتصال تست را تعریف کند:

کاتلین

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsActivityTest {

  @Module
  @InstallIn(SingletonComponent::class)
  abstract class TestModule {

    @Singleton
    @Binds
    abstract fun bindAnalyticsService(
      fakeAnalyticsService: FakeAnalyticsService
    ): AnalyticsService
  }

  ...
}

جاوا

@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 برای تست‌های instrumented قرار دهید. توصیه می‌شود در صورت امکان از @TestInstallIn استفاده کنید.

مقید کردن مقادیر جدید

از حاشیه‌نویسی @BindValue برای اتصال آسان فیلدهای موجود در تست خود به نمودار وابستگی Hilt استفاده کنید. یک فیلد را با @BindValue حاشیه‌نویسی کنید و آن فیلد تحت نوع فیلد اعلام شده با هر توصیف‌کننده‌ای که برای آن فیلد وجود دارد، محدود خواهد شد.

در مثال AnalyticsService ، می‌توانید با استفاده از @BindValue AnalyticsService با یک متغیر جعلی جایگزین کنید:

کاتلین

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsActivityTest {

  @BindValue @JvmField
  val analyticsService: AnalyticsService = FakeAnalyticsService()

  ...
}

جاوا

@UninstallModules(AnalyticsModule.class)
@HiltAndroidTest
class SettingsActivityTest {

  @BindValue AnalyticsService analyticsService = FakeAnalyticsService();

  ...
}

این کار با فراهم کردن امکان انجام همزمان هر دو کار، جایگزینی یک متغیر و ارجاع به یک متغیر در تست شما را ساده می‌کند.

@BindValue با qualifierها و سایر حاشیه‌نویسی‌های تست کار می‌کند. برای مثال، اگر از کتابخانه‌های تست مانند Mockito استفاده می‌کنید، می‌توانید آن را در یک تست Robolectric به صورت زیر استفاده کنید:

کاتلین

...
class SettingsActivityTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

جاوا

...
class SettingsActivityTest {
  ...
  @BindValue @ExampleQualifier @Mock ExampleCustomType qualifiedVariable;

  // Robolectric tests here
}

اگر نیاز به اضافه کردن multibinding دارید، می‌توانید به جای @BindValue از حاشیه‌نویسی‌های @BindValueIntoSet و @BindValueIntoMap استفاده کنید. @BindValueIntoMap شما را ملزم می‌کند که فیلد را با حاشیه‌نویسی کلید نقشه نیز حاشیه‌نویسی کنید.

موارد خاص

هیلت همچنین ویژگی‌هایی را برای پشتیبانی از موارد استفاده غیراستاندارد ارائه می‌دهد.

برنامه سفارشی برای آزمون‌ها

اگر به دلیل نیاز برنامه آزمایشی خود به بسط برنامه دیگری، نمی‌توانید از HiltTestApplication استفاده کنید، یک کلاس یا رابط جدید را با @CustomTestApplication حاشیه‌نویسی کنید و مقدار کلاس پایه‌ای را که می‌خواهید برنامه Hilt تولید شده بسط دهد، به آن ارسال کنید.

@CustomTestApplication یک کلاس Application آماده برای تست با Hilt ایجاد می‌کند که از برنامه‌ای که به عنوان پارامتر ارسال کرده‌اید، ارث‌بری می‌کند.

کاتلین

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

جاوا

@CustomTestApplication(BaseApplication.class)
interface HiltTestApplication { }

در این مثال، Hilt یک Application با نام HiltTestApplication_Application تولید می‌کند که کلاس BaseApplication را ارث‌بری می‌کند. به‌طورکلی، نام برنامه تولیدشده، نام کلاس حاشیه‌نویسی‌شده‌ای است که با _Application به آن اضافه شده است. شما باید برنامه تست Hilt تولیدشده را برای اجرا در تست‌های instrumented یا تست‌های Robolectric خود، همانطور که در Test application توضیح داده شده است، تنظیم کنید.

چندین شیء TestRule در تست ابزاری شما

اگر اشیاء TestRule دیگری در تست خود دارید، روش‌های متعددی برای اطمینان از اینکه همه قوانین با هم کار می‌کنند، وجود دارد.

می‌توانید قوانین را به صورت زیر کنار هم قرار دهید:

کاتلین

@HiltAndroidTest
class SettingsActivityTest {

  @get:Rule
  var rule = RuleChain.outerRule(HiltAndroidRule(this)).
        around(SettingsActivityTestRule(...))

  // UI tests here.
}

جاوا

@HiltAndroidTest
public final class SettingsActivityTest {

  @Rule public RuleChain rule = RuleChain.outerRule(new HiltAndroidRule(this))
        .around(new SettingsActivityTestRule(...));

  // UI tests here.
}

به عنوان یک روش جایگزین، می‌توانید از هر دو قانون در یک سطح استفاده کنید، به شرطی که HiltAndroidRule ابتدا اجرا شود. ترتیب اجرا را با استفاده از ویژگی order در حاشیه‌نویسی @Rule مشخص کنید. این روش فقط در JUnit نسخه ۴.۱۳ یا بالاتر کار می‌کند:

کاتلین

@HiltAndroidTest
class SettingsActivityTest {

  @get:Rule(order = 0)
  var hiltRule = HiltAndroidRule(this)

  @get:Rule(order = 1)
  var settingsActivityTestRule = SettingsActivityTestRule(...)

  // UI tests here.
}

جاوا

@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 استفاده کنید.

قبل از در دسترس بودن کامپوننت singleton، از یک نقطه ورود استفاده کنید.

حاشیه‌نویسی @EarlyEntryPoint زمانی که نیاز به ایجاد یک نقطه ورود Hilt قبل از در دسترس قرار گرفتن کامپوننت singleton در تست Hilt باشد، یک روزنه فرار فراهم می‌کند.

اطلاعات بیشتر در مورد @EarlyEntryPoint در مستندات Hilt موجود است.