מדריך לבדיקות ב-Hilt (תצוגות)

מושגים והטמעה של Jetpack Compose

אחד מהיתרונות של שימוש ב-frameworks של החדרת תלות כמו Hilt הוא שהם מקלים על בדיקת הקוד.

בדיקות יחידה (unit testing)

לא צריך את Hilt לבדיקות יחידה, כי כשבודקים מחלקה שמשתמשת בהחדרה ל-constructor, לא צריך להשתמש ב-Hilt כדי ליצור מופע של המחלקה הזו. במקום זאת, אתם יכולים לקרוא ישירות ל-constructor של הכיתה על ידי העברת יחסי תלות מזויפים או מדומים, בדיוק כמו שהייתם עושים אם ה-constructor לא היה מסומן:

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

הגדרת בדיקת ממשק משתמש

צריך להוסיף הערה לכל בדיקת ממשק משתמש שמשתמשת ב-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.
}

אם אתם משתמשים בפלאגין של Android Gradle בגרסה ישנה יותר מ-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 ב-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
}

אם אתם צריכים להוסיף multibinding, אתם יכולים להשתמש באנוטציות @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. האפשרות הזו פועלת רק ב-JUnit בגרסה 4.13 ואילך:

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.