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

אחד היתרונות של שימוש ב-frameworks להזרקת תלות כמו 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.56.2'
    // ...with Kotlin.
    kaptTest 'com.google.dagger:hilt-android-compiler:2.56.2'
    // ...with Java.
    testAnnotationProcessor 'com.google.dagger:hilt-android-compiler:2.56.2'


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

Kotlin

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


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

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

צריך להוסיף את ההערה @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. ההוראות הבאות לא ספציפיות ל-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 Plugin שקטנה מ-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
}

אם אתם צריכים להוסיף 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.

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

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

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