راهنمای آزمایش Hilt (نماها)

مفاهیم و پیاده‌سازی Jetpack Compose

یکی از مزایای استفاده از چارچوب‌های تزریق وابستگی مانند 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 را برای استفاده در آزمایش‌ها ارائه می‌دهد. اگر آزمایش‌هایتان به برنامه پایه دیگری نیاز دارد، برنامه سفارشی برای آزمایش‌ها را ببینید.

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

شیک

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"
    }
}
تنظیم برنامه آزمایشی در آزمایش‌های Robolectric

اگر از 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.
}

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

برای مثال، فرض کنید کد تولید شما اتصالی را برای 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
  );
}

جایگزین کردن اتصال در آزمایشی تکی

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

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

کاتلین

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

درحال اتصال مقادیر جدید

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

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

کاتلین

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

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

  ...
}

جاوا

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

  @BindValue AnalyticsService analyticsService = FakeAnalyticsService();

  ...
}

این کار هم جایگزین کردن اتصال و هم ارجاع دادن به اتصال را در آزمایشتان ساده می‌کند چون به شما امکان می‌دهد هر دو کار را هم‌زمان انجام دهید.

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

کاتلین

...
class SettingsActivityTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

جاوا

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

  // Robolectric tests here
}

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

موارد خاص

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

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

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

‫@CustomTestApplication یک کلاس Application آماده برای آزمایش با Hilt تولید می‌کند که برنامه‌ای را که به‌عنوان پارامتر ارسال کرده‌اید گسترش می‌دهد.

کاتلین

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

جاوا

@CustomTestApplication(BaseApplication.class)
interface HiltTestApplication { }

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

چندین شیء 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.
}

launchFragmentInContainer

استفاده از launchFragmentInContainer از کتابخانه androidx.fragment:fragment-testing با Hilt امکان‌پذیر نیست، زیرا به فعالیتی متکی است که با @AndroidEntryPoint حاشیه‌نویسی نشده است.

درعوض، از کد launchFragmentInHiltContainer از مخزن architecture-samples GitHub استفاده کنید.