מדריך לבדיקת נסתר

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

בדיקות יחידה

אין צורך ב-Hilt לבדיקות יחידה, כי כשבודקים מחלקה שמשתמשת בהחדרה ל-constructor, לא צריך להשתמש ב-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(...)
  }
}

אותו עיקרון חל על מחלקות ViewModel שמתקבלות על ידי קריאה ל-hiltViewModel() בקומפוזיציות. בבדיקות יחידה, יוצרים את ViewModel ישירות באמצעות זיופים. למידע על אופן העברת מצב מ-ViewModel לרכיבים קומפוזביליים, אפשר לעיין במאמרים מצב ב-Jetpack Compose ואיפה כדאי להעלות את המצב.

בדיקות מקצה לקצה

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

הוספת יחסי תלות לבדיקה

כדי להשתמש ב-Hilt בבדיקות, צריך לכלול את התלות hilt-android-testing בפרויקט:

dependencies {
    // For Robolectric tests.
    testImplementation("com.google.dagger:hilt-android-testing:2.57.1")
    kspTest("com.google.dagger:hilt-android-compiler:2.57.1")

    // For instrumented tests.
    androidTestImplementation("com.google.dagger:hilt-android-testing:2.57.1")
    kspAndroidTest("com.google.dagger:hilt-android-compiler:2.57.1")

    // Compose UI test rule.
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")
    debugImplementation("androidx.compose.ui:ui-test-manifest")

}

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

צריך להוסיף את ההערה @HiltAndroidTest לכל בדיקת ממשק משתמש שמשתמשת ב-Hilt. ההערה הזו אחראית ליצירת רכיבי Hilt לכל בדיקה.

בנוסף, צריך להוסיף את HiltAndroidRule לכיתת הבדיקה. הוא מנהל את מצב הרכיבים ומשמש להוספת תלות בבדיקה:

@HiltAndroidTest
class SettingsScreenTest {

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

    @get:Rule(order = 1)
    val composeRule = createAndroidComposeRule<HiltTestActivity>()

    // Compose UI tests here.
}

בשלב הבא, צריך להגדיר את המחלקה Application שהספרייה Hilt יוצרת באופן אוטומטי בשבילכם.

כדי לאפשר ל-Hilt להוסיף תלויות, צריך ליצור פעילות ריקה בשם HiltTestActivity בערכת המקור androidTest ולהוסיף לה את ההערה @AndroidEntryPoint. ‫createAndroidComposeRule משתמש בפעילות הזו כמארח של התוכן המורכב.

אפליקציית בדיקה

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

לאחר מכן, מגדירים את כלי ההרצה הזה בקובץ Gradle, כמו שמתואר במדריך בנושא בדיקת יחידות עם מכשור. חשוב לוודא שמשתמשים בנתיב המלא של המחלקה:

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 SettingsScreenTest {

  @get:Rule
  var hiltRule = HiltAndroidRule(this)

  // Robolectric tests here.
}

בדיקת תכונות

אחרי שספריית Hilt מוכנה לשימוש בבדיקות, אפשר להשתמש בכמה תכונות כדי להתאים אישית את תהליך הבדיקה.

החדרת סוגים לבדיקות

כדי להוסיף סוגים לבדיקה, משתמשים ב-@Inject להחדרה לשדה. כדי להורות ל-Hilt לאכלס את השדות @Inject, קוראים ל-hiltRule.inject().

לדוגמה, כך נראה מבחן עם מכשור:

@HiltAndroidTest
class SettingsScreenTest {

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

    @get:Rule(order = 1)
    val composeRule = createAndroidComposeRule<HiltTestActivity>()

    @Inject
    lateinit var analyticsAdapter: AnalyticsAdapter

    @Before
    fun init() {
        hiltRule.inject()
    }

    @Test
    fun settingsScreen_showsTitle() {
        composeRule.setContent {
            SettingsScreen()
        }
        composeRule.onNodeWithText("Settings").assertIsDisplayed()
        // analyticsRepository is available here.
    }
}

החלפת קישור

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

לדוגמה, נניח שקוד הייצור שלכם מכריז על קשירה ל-AnalyticsService באופן הבא:

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

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

כדי להחליף את הקישור AnalyticsService בבדיקות, יוצרים מודול Hilt חדש בתיקייה test או androidTest עם התלות המדומה ומסמנים אותו באמצעות @TestInstallIn. במקום זאת, כל הבדיקות בתיקייה הזו מקבלות את התלות המזויפת.

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

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

בדרך כלל, פונקציות composable צורכות את התלות האלה באופן עקיף דרך ViewModel שמתקבל באמצעות hiltViewModel(), ולכן מספיק להחליף את הקישור ב-Hilt. הקומפוזבילי שנבדק מזהה את הזיוף באופן אוטומטי.

החלפת קשירה בבדיקה אחת

כדי להחליף קישור בבדיקה אחת במקום בכל הבדיקות, מסירים התקנה של מודול Hilt מבדיקה באמצעות ההערה @UninstallModules ויוצרים מודול בדיקה חדש בתוך הבדיקה.

בהמשך AnalyticsService לדוגמה מהגרסה הקודמת, מתחילים בהוראה ל-Hilt להתעלם ממודול הייצור באמצעות הערת @UninstallModules במחלקת הבדיקה:

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

בשלב הבא, צריך להחליף את הקישור. יוצרים מודול חדש בכיתת הבדיקה שמגדיר את קישור הבדיקה:

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

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

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

  // ...
}

הפעולה הזו מחליפה את הקישור רק עבור מחלקת בדיקה אחת. אם רוצים להחליף את הקישור לכל מחלקות הבדיקה, משתמשים בהערה @TestInstallIn מהקטע שלמעלה. אפשר גם להוסיף את הקישור לבדיקה למודול test לבדיקות Robolectric, או למודול androidTest לבדיקות עם מכשור. ההמלצה היא להשתמש ב@TestInstallIn בכל הזדמנות.

קישור ערכים חדשים

אפשר להשתמש בהערה @BindValue כדי לקשר בקלות שדות בבדיקה לגרף התלות של Hilt. אם מוסיפים לשדה את ההערה @BindValue, השדה יקבל את סוג השדה שהוגדר לו, עם כל המאפיינים הרלוונטיים.

בדוגמה AnalyticsService, אפשר להחליף את AnalyticsService ב-fake באמצעות @BindValue:

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

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

  ...
}

כך אפשר גם להחליף את הקישור וגם להפנות לקישור בבדיקה, ולעשות את שתי הפעולות בו-זמנית.

@BindValue פועל עם מסננים והערות אחרות לבדיקה. לדוגמה, אם משתמשים בספריות בדיקה כמו Mockito, אפשר להשתמש בהן בבדיקת Robolectric באופן הבא:

...
class SettingsScreenTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

אם אתם צריכים להוסיף multibinding, אתם יכולים להשתמש באנוטציות @BindValueIntoSet ו-@BindValueIntoMap במקום ב-@BindValue. @BindValueIntoMap מחייב גם להוסיף לשדה הערה של מפתח מיפוי.

מקרים מיוחדים

‫Hilt מספקת גם תכונות לתמיכה בתרחישי שימוש לא סטנדרטיים.

אפליקציה מותאמת אישית לבדיקות

אם אי אפשר להשתמש ב-HiltTestApplication כי אפליקציית הבדיקה צריכה להרחיב אפליקציה אחרת, צריך להוסיף הערה למחלקה או לממשק חדשים עם @CustomTestApplication ולהעביר את הערך של מחלקת הבסיס שרוצים שהאפליקציה שנוצרה על ידי Hilt תרחיב.

@CustomTestApplication ייצור מחלקה Application שמוכנה לבדיקה באמצעות Hilt, שמרחיבה את האפליקציה שהעברתם כפרמטר.

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

בדוגמה, Hilt יוצר רכיב בשם Application HiltTestApplication_Application שהוא הרחבה של המחלקה BaseApplication. בדרך כלל, השם של האפליקציה שנוצרת הוא השם של המחלקה עם ההערות בתוספת _Application. צריך להגדיר את אפליקציית הבדיקה שנוצרה על ידי Hilt כך שתפעל בבדיקות עם מכשור או בבדיקות Robolectric, כמו שמתואר במאמר אפליקציית בדיקה.

אובייקטים מרובים של TestRule בבדיקה עם מכשור

בבדיקות של ממשקי משתמש ב-Compose כבר משולב HiltAndroidRule עם כלל בדיקה של Compose, כמו createAndroidComposeRule. אם יש לכם עוד אובייקטים של TestRule, הקפידו להריץ קודם את HiltAndroidRule. מצהירים על סדר ההרצה באמצעות המאפיין order ב-@Rule:

@HiltAndroidTest
class SettingsScreenTest {

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

  @get:Rule(order = 1)
  val composeRule = createAndroidComposeRule<HiltTestActivity>()

  @get:Rule(order = 2)
  val otherRule = SomeOtherRule()

  // UI tests here.
}

אפשר גם להוסיף את הכללים בין התגים RuleChain, ולהשתמש בתג HiltAndroidRule ככלל החיצוני.

@HiltAndroidTest
class SettingsScreenTest {

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

  // UI tests here.
}

שימוש בנקודת כניסה לפני שהרכיב היחיד זמין

ההערה @EarlyEntryPoint מספקת פתרון במקרה שצריך ליצור נקודת כניסה של Hilt לפני שהרכיב היחיד זמין בבדיקה של Hilt.

מידע נוסף על @EarlyEntryPoint זמין במאמרי העזרה בנושא Hilt.

מקורות מידע נוספים

מידע נוסף על בדיקות זמין במקורות המידע הבאים:

מאמרי עזרה

צפיות בתוכן