ข้อดีอย่างหนึ่งของการใช้เฟรมเวิร์กการแทรกทรัพยากร Dependency เช่น Hilt คือ ช่วยให้การทดสอบโค้ดง่ายขึ้น
การทดสอบหน่วย
คุณไม่จำเป็นต้องใช้ Hilt สำหรับการทดสอบหน่วย เนื่องจากเมื่อทดสอบคลาสที่ใช้ การแทรกผ่านตัวสร้าง คุณไม่จำเป็นต้องใช้ Hilt เพื่อสร้างอินสแตนซ์ของคลาสนั้น แต่คุณสามารถเรียกใช้เครื่องมือสร้างคลาสได้โดยตรงโดยส่งทรัพยากร Dependency ปลอมหรือทรัพยากร Dependency จำลอง เช่นเดียวกับที่คุณทำหากไม่ได้ใส่คำอธิบายประกอบเครื่องมือสร้าง
@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() ใน
Composables ด้วย ใน Unit Test ให้สร้าง ViewModel โดยตรงด้วยข้อมูลจำลอง
ดูข้อมูลเกี่ยวกับวิธีที่สถานะไหลจาก ViewModel ไปยัง Composable ได้ที่สถานะและ Jetpack Compose และตำแหน่งที่จะย้ายสถานะ
การทดสอบตั้งแต่ต้นจนจบ
สำหรับการทดสอบการผสานรวม Hilt จะแทรกการอ้างอิงเหมือนกับในโค้ดเวอร์ชันที่ใช้งานจริง การทดสอบด้วย Hilt ไม่ต้องมีการบำรุงรักษาเนื่องจาก Hilt จะสร้างชุดคอมโพเนนต์ใหม่สำหรับการทดสอบแต่ละครั้งโดยอัตโนมัติ
การเพิ่มทรัพยากร Dependency ของการทดสอบ
หากต้องการใช้ Hilt ในการทดสอบ ให้รวมทรัพยากร Dependency 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") }
การตั้งค่าการทดสอบ UI
คุณต้องใส่คำอธิบายประกอบในการทดสอบ UI ที่ใช้ 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 แทรกทรัพยากร Dependency คุณต้องสร้างกิจกรรมว่างชื่อ
HiltTestActivity ในชุดซอร์ส androidTest และใส่คำอธิบายประกอบด้วย
@AndroidEntryPoint createAndroidComposeRule จะใช้กิจกรรมนี้เป็นโฮสต์สำหรับเนื้อหาที่ประกอบได้
แอปพลิเคชันทดสอบ
คุณต้องเรียกใช้การทดสอบที่มีการตรวจสอบซึ่งใช้ Hilt ในออบเจ็กต์ Application
ที่รองรับ Hilt ไลบรารีมี HiltTestApplication สำหรับใช้ในการทดสอบ
หากการทดสอบต้องใช้แอปพลิเคชันพื้นฐานอื่น โปรดดูแอปพลิเคชันที่กำหนดเองสำหรับการทดสอบ
คุณต้องตั้งค่าแอปพลิเคชันทดสอบให้ทำงานในการทดสอบที่ใช้เครื่องมือหรือการทดสอบ Robolectric วิธีการต่อไปนี้ไม่ได้ เจาะจงสำหรับ Hilt แต่เป็นหลักเกณฑ์ทั่วไปเกี่ยวกับวิธีระบุ แอปพลิเคชันที่กำหนดเองเพื่อเรียกใช้ในการทดสอบ
ตั้งค่าแอปพลิเคชันทดสอบในการทดสอบแบบมีเครื่องมือ
หากต้องการใช้แอปพลิเคชันทดสอบ Hilt ในการทดสอบที่มีการตรวจสอบ คุณต้องกำหนดค่า Test Runner ใหม่ ซึ่งจะทำให้ Hilt ทำงานกับการทดสอบที่วัดได้ทั้งหมดในโปรเจ็กต์ ทำตามขั้นตอนต่อไปนี้
- สร้างคลาสที่กำหนดเองซึ่งขยาย
AndroidJUnitRunnerใน โฟลเดอร์androidTest - ลบล้าง
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) } }
จากนั้นกำหนดค่า Test Runner นี้ในไฟล์ Gradle ตามที่อธิบายไว้ใน คู่มือการทดสอบหน่วยแบบมีเครื่องมือ ตรวจสอบว่า คุณใช้ classpath แบบเต็ม
android { defaultConfig { // Replace com.example.android.dagger with your class path. testInstrumentationRunner = "com.example.android.dagger.CustomTestRunner" } }
ตั้งค่าแอปพลิเคชันทดสอบในการทดสอบ Robolectric
หากใช้ Robolectric เพื่อทดสอบเลเยอร์ UI คุณจะระบุแอปพลิเคชันที่จะใช้ในไฟล์ 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. } }
แทนที่การเชื่อมโยง
หากต้องการแทรกอินสแตนซ์จำลองหรืออินสแตนซ์ทดสอบของ Dependency คุณต้องบอก Hilt ไม่ให้ใช้การเชื่อมโยงที่ใช้ในโค้ดเวอร์ชันที่ใช้งานจริง และให้ใช้การเชื่อมโยงอื่นแทน หากต้องการแทนที่การเชื่อมโยง คุณต้องแทนที่โมดูลที่มีการเชื่อมโยงด้วยโมดูลทดสอบที่มีการเชื่อมโยงที่คุณต้องการใช้ในการทดสอบ
ตัวอย่างเช่น สมมติว่าโค้ดเวอร์ชันที่ใช้งานจริงประกาศการเชื่อมโยงสำหรับ
AnalyticsService ดังนี้
@Module @InstallIn(SingletonComponent::class) abstract class AnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( analyticsServiceImpl: AnalyticsServiceImpl ): AnalyticsService }
หากต้องการแทนที่AnalyticsServiceการเชื่อมโยงในการทดสอบ ให้สร้างโมดูล Hilt ใหม่ในโฟลเดอร์ test หรือ androidTest ที่มี Dependency ปลอม แล้วใส่คำอธิบายประกอบด้วย @TestInstallIn ระบบจะแทรกการทดสอบทั้งหมดในโฟลเดอร์นั้นด้วยการขึ้นต่อปลอมแทน
@Module @TestInstallIn( components = [SingletonComponent::class], replaces = [AnalyticsModule::class] ) abstract class FakeAnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService }
เนื่องจาก Composable มักใช้ทรัพยากร Dependency เหล่านี้โดยอ้อมผ่าน ViewModel ที่ได้จาก hiltViewModel() การแทนที่การเชื่อมโยงใน Hilt จึงเพียงพอ Composable ที่อยู่ภายใต้การทดสอบจะเลือกใช้การจำลองโดยอัตโนมัติ
แทนที่การเชื่อมโยงในการทดสอบเดียว
หากต้องการแทนที่การเชื่อมโยงในการทดสอบเดียวแทนที่จะเป็นการทดสอบทั้งหมด ให้ถอนการติดตั้งโมดูล 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 เพื่อเชื่อมโยงฟิลด์ในการทดสอบกับกราฟทรัพยากร Dependency ของ Hilt ได้อย่างง่ายดาย ใส่คำอธิบายประกอบในช่องด้วย @BindValue แล้วระบบจะเชื่อมโยงช่องดังกล่าวภายใต้
ประเภทช่องที่ประกาศพร้อมตัวระบุที่มีอยู่สำหรับช่องนั้น
ในAnalyticsServiceตัวอย่าง คุณสามารถแทนที่ AnalyticsService ด้วย a
fake โดยใช้ @BindValue ดังนี้
@UninstallModules(AnalyticsModule::class) @HiltAndroidTest class SettingsScreenTest { @BindValue @JvmField val analyticsService: AnalyticsService = FakeAnalyticsService() ... }
ซึ่งจะช่วยลดความซับซ้อนทั้งในการแทนที่การเชื่อมโยงและการอ้างอิงการเชื่อมโยงในการทดสอบ โดยให้คุณทำทั้ง 2 อย่างได้พร้อมกัน
@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 ที่สร้างขึ้นให้ทำงานใน Instrumented Test หรือ Robolectric Test ตามที่อธิบายไว้ในแอปพลิเคชันทดสอบ
ออบเจ็กต์ TestRule หลายรายการในการทดสอบที่มีเครื่องมือวัด
การทดสอบ UI ของ 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. }
ใช้จุดแรกเข้าก่อนที่คอมโพเนนต์ Singleton จะพร้อมใช้งาน
คำอธิบายประกอบ @EarlyEntryPoint จะเป็นทางออกในกรณีที่ต้องสร้างจุดแรกเข้าของ Hilt
ก่อนที่คอมโพเนนต์ Singleton จะพร้อมใช้งานในการทดสอบ Hilt
ดูข้อมูลเพิ่มเติมเกี่ยวกับ @EarlyEntryPoint ได้ในเอกสารประกอบของ Hilt
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบได้ที่แหล่งข้อมูลเพิ่มเติมต่อไปนี้