हिल्ट टेस्टिंग गाइड

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

अपने कंपोज़ेबल में hiltViewModel() को कॉल करके मिली ViewModel क्लास पर भी यही बात लागू होती है. यूनिट टेस्ट में, 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")

}

यूज़र इंटरफ़ेस (यूआई) टेस्ट सेटअप

आपको Hilt का इस्तेमाल करने वाले किसी भी यूज़र इंटरफ़ेस (यूआई) टेस्ट को @HiltAndroidTest के साथ एनोटेट करना होगा. यह एनोटेशन, हर टेस्ट के लिए 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 को डिपेंडेंसी इंजेक्ट करने की अनुमति देने के लिए, आपको अपने androidTest सोर्स सेट में HiltTestActivity नाम की एक खाली गतिविधि बनानी होगी. साथ ही, इसे @AndroidEntryPoint के साथ एनोटेट करना होगा. createAndroidComposeRule, इस गतिविधि का इस्तेमाल आपके कंपोज़ेबल कॉन्टेंट के होस्ट के तौर पर करता है.

टेस्ट ऐप्लिकेशन

आपको Hilt का इस्तेमाल करने वाले इंस्ट्रुमेंटेड टेस्ट, ऐसे Application ऑब्जेक्ट में चलाने होंगे जो Hilt के साथ काम करता हो. लाइब्रेरी, टेस्ट में इस्तेमाल करने के लिए HiltTestApplication उपलब्ध कराती है. अगर आपके टेस्ट के लिए किसी दूसरे बेस ऐप्लिकेशन की ज़रूरत है, तो टेस्ट के लिए कस्टम ऐप्लिकेशन देखें.

आपको अपने टेस्ट ऐप्लिकेशन को इंस्ट्रुमेंटेड टेस्ट या Robolectric टेस्ट में चलाने के लिए सेट करना होगा. यहां दिए गए निर्देश, खास तौर पर Hilt के लिए नहीं हैं. हालांकि, ये सामान्य दिशा-निर्देश हैं. इनसे यह पता चलता है कि टेस्ट में चलाने के लिए, कस्टम ऐप्लिकेशन को कैसे तय किया जाए.

इंस्ट्रुमेंटेड टेस्ट में टेस्ट ऐप्लिकेशन सेट करना

इंस्ट्रुमेंटेड टेस्ट में Hilt टेस्ट ऐप्लिकेशन का इस्तेमाल करने के लिए, आपको एक नया टेस्ट रनर कॉन्फ़िगर करना होगा. इससे Hilt, आपके प्रोजेक्ट में मौजूद सभी इंस्ट्रुमेंटेड टेस्ट के लिए काम करता है. यह तरीका अपनाएं:

  1. androidTest फ़ोल्डर में, AndroidJUnitRunner को एक्सटेंड करने वाली कस्टम क्लास बनाएं.
  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

इसके अलावा, Robolectric के @Config एनोटेशन का इस्तेमाल करके, हर टेस्ट के लिए ऐप्लिकेशन को अलग-अलग कॉन्फ़िगर किया जा सकता है:

@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 बाइंडिंग को बदलने के लिए, test या androidTest फ़ोल्डर में नकली डिपेंडेंसी के साथ नया Hilt मॉड्यूल बनाएं. इसके बाद, उसे @TestInstallIn के साथ एनोटेट करें. इसके बजाय, उस फ़ोल्डर में मौजूद सभी टेस्ट में नकली डिपेंडेंसी इंजेक्ट की जाती है.

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

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

आम तौर पर, कंपोज़ेबल इन डिपेंडेंसी का इस्तेमाल, hiltViewModel() से मिले ViewModel के ज़रिए करते हैं. इसलिए, Hilt में बाइंडिंग को बदलना ही काफ़ी है. टेस्ट किया जा रहा कंपोज़ेबल, नकली डेटा को अपने-आप चुन लेता है.

किसी एक टेस्ट में बाइंडिंग को बदलना

सभी टेस्ट के बजाय किसी एक टेस्ट में बाइंडिंग को बदलने के लिए, @UninstallModules एनोटेशन का इस्तेमाल करके किसी टेस्ट से Hilt मॉड्यूल को अनइंस्टॉल करें. इसके बाद, टेस्ट में एक नया टेस्ट मॉड्यूल बनाएं.

पिछले वर्शन के 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 एनोटेशन का इस्तेमाल करें. इसके अलावा, Robolectric टेस्ट के लिए, टेस्ट बाइंडिंग को test मॉड्यूल में या इंस्ट्रुमेंटेड टेस्ट के लिए, androidTest मॉड्यूल में डाला जा सकता है. हमारा सुझाव है कि जब भी संभव हो, @TestInstallIn का इस्तेमाल करें.

नई वैल्यू बाइंड करना

अपने टेस्ट में फ़ील्ड को Hilt डिपेंडेंसी ग्राफ़ में आसानी से बाइंड करने के लिए, @BindValue एनोटेशन का इस्तेमाल करें. किसी फ़ील्ड को @BindValue के साथ एनोटेट करें. इससे वह फ़ील्ड, एलान किए गए फ़ील्ड टाइप के तहत बाउंड हो जाएगा. साथ ही, उस फ़ील्ड के लिए मौजूद सभी क्वालिफ़ायर भी बाउंड हो जाएंगे.

AnalyticsService के उदाहरण में, @BindValue का इस्तेमाल करके AnalyticsService को नकली से बदला जा सकता है:

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

अगर आपको मल्टीबाइंडिंग जोड़नी है, तो @BindValue के बजाय @BindValueIntoSet और @BindValueIntoMap एनोटेशन का इस्तेमाल किया जा सकता है. @BindValueIntoMap के लिए, आपको फ़ील्ड को मैप की एनोटेशन के साथ एनोटेट करना होगा.

विशेष मामले

Hilt, इस्तेमाल के ऐसे उदाहरणों के लिए भी सुविधाएं उपलब्ध कराता है जो स्टैंडर्ड नहीं हैं.

जांच के लिए कस्टम ऐप्लिकेशन

अगर HiltTestApplication का इस्तेमाल नहीं किया जा सकता, क्योंकि आपके टेस्ट ऐप्लिकेशन को किसी दूसरे ऐप्लिकेशन को एक्सटेंड करना है, तो @CustomTestApplication के साथ नई क्लास या इंटरफ़ेस को एनोटेट करें. साथ ही, उस बेस क्लास की वैल्यू पास करें जिसे जनरेट किए गए Hilt ऐप्लिकेशन को एक्सटेंड करना है.

@CustomTestApplication, Hilt के साथ टेस्ट करने के लिए तैयार Application क्लास जनरेट करेगा. यह क्लास, पैरामीटर के तौर पर पास किए गए ऐप्लिकेशन को एक्सटेंड करती है.

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

उदाहरण के लिए, Hilt Application जनरेट करता है, जिसका नाम HiltTestApplication_Application है. यह BaseApplication क्लास को एक्सटेंड करता है. आम तौर पर, जनरेट किए गए ऐप्लिकेशन का नाम, एनोटेट की गई क्लास के नाम के साथ _Application जोड़कर बनाया जाता है. आपको जनरेट किए गए Hilt टेस्ट ऐप्लिकेशन को इंस्ट्रुमेंटेड टेस्ट या Robolectric टेस्ट में चलाने के लिए सेट करना होगा. इसके बारे में टेस्ट ऐप्लिकेशन में बताया गया है.

आपके इंस्ट्रुमेंटेड टेस्ट में कई TestRule ऑब्जेक्ट मौजूद हैं

Compose यूज़र इंटरफ़ेस (यूआई) टेस्ट में, HiltAndroidRule को Compose टेस्ट के नियम के साथ पहले से ही जोड़ा जाता है. जैसे, createAndroidComposeRule. अगर आपके पास अन्य TestRule ऑब्जेक्ट हैं, तो पक्का करें कि HiltAndroidRule पहले चले. @Rule पर order एट्रिब्यूट का इस्तेमाल करके, कार्रवाइयों को पूरा करने का क्रम तय करें:

@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 का दस्तावेज़ पढ़ें.

अन्य संसाधन

टेस्टिंग के बारे में ज़्यादा जानने के लिए, ये अतिरिक्त संसाधन देखें:

दस्तावेज़

कॉन्टेंट देखना