ข้อดีอย่างหนึ่งของการใช้เฟรมเวิร์กการแทรกทรัพยากร 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) } }
จากนั้นกำหนดค่าโปรแกรมเรียกใช้การทดสอบนี้ในไฟล์ 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 ระบบจะแทรกการทดสอบทั้งหมดในโฟลเดอร์นั้นด้วย
Dependency ปลอมแทน
@Module @TestInstallIn( components = [SingletonComponent::class], replaces = [AnalyticsModule::class] ) abstract class FakeAnalyticsModule { @Singleton @Binds abstract fun bindAnalyticsService( fakeAnalyticsService: FakeAnalyticsService ): AnalyticsService }
เนื่องจากโดยปกติแล้ว Composable จะใช้ทรัพยากร Dependency เหล่านี้โดยอ้อมผ่าน ViewModel ที่ได้จาก hiltViewModel() การแทนที่ Binding ใน 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 หลายรายการในการทดสอบที่มีเครื่องมือวัด
การทดสอบ Compose UI จะรวม 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
แหล่งข้อมูลเพิ่มเติม
ดูข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบได้ที่แหล่งข้อมูลเพิ่มเติมต่อไปนี้