คู่มือการทดสอบ Hilt

ประโยชน์อย่างหนึ่งของการใช้เฟรมเวิร์กการแทรกทรัพยากร Dependency เช่น Hilt คือช่วยให้ทดสอบโค้ดได้ง่ายขึ้น

Unit Test

Hilt ไม่จำเป็นสำหรับการทำ Unit Test เนื่องจากเมื่อทดสอบคลาสที่ใช้การแทรกเครื่องมือสร้าง คุณไม่จำเป็นต้องใช้ Hilt เพื่อสร้างอินสแตนซ์ของคลาสนั้น แต่คุณสามารถเรียกใช้ตัวสร้างคลาสได้โดยตรงโดยส่งผ่านทรัพยากร 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() ใน Composable ด้วย ใน Unit Test ให้สร้าง ViewModel โดยตรงด้วยข้อมูลจำลอง ดูข้อมูลเกี่ยวกับวิธีที่สถานะส่งผ่านจาก ViewModel ไปยัง Composable ได้ที่สถานะและ Jetpack Compose และตำแหน่งที่จะยกสถานะขึ้น

การทดสอบตั้งแต่ต้นจนจบ

สำหรับการทดสอบการผสานรวม Hilt จะแทรกทรัพยากร Dependency เหมือนกับที่ทำในโค้ดเวอร์ชันที่ใช้งานจริง การทดสอบด้วย 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 จะใช้กิจกรรมนี้เป็นโฮสต์สำหรับเนื้อหา Composable ของคุณ

แอปพลิเคชันทดสอบ

คุณต้องดำเนินการทดสอบการวัดคุมซึ่งใช้ Hilt ในออบเจ็กต์ Application ที่รองรับ Hilt ไลบรารีมี HiltTestApplication สำหรับใช้ในการทดสอบ หากการทดสอบต้องใช้แอปพลิเคชันพื้นฐานอื่น โปรดดูหัวข้อแอปพลิเคชันที่กำหนดเองสำหรับการทดสอบ

คุณต้องตั้งค่าแอปพลิเคชันทดสอบให้ทำงานในการทดสอบที่มีการวัดคุมหรือการทดสอบ Robolectric วิธีการต่อไปนี้ไม่ได้เจาะจงเฉพาะ Hilt แต่เป็นหลักเกณฑ์ทั่วไปเกี่ยวกับวิธีระบุแอปพลิเคชันที่กำหนดเองเพื่อเรียกใช้ในการทดสอบ

ตั้งค่าแอปพลิเคชันทดสอบในการทดสอบการวัดคุม

หากต้องการใช้แอปพลิเคชันทดสอบ Hilt ในการทดสอบที่มีการวัดคุม คุณต้องกำหนดค่า Test Runner ใหม่ ซึ่งจะทำให้ 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)
    }
}

ถัดไป ให้กำหนดค่า Test Runner นี้ในไฟล์ Gradle ตามที่อธิบายไว้ในคู่มือการทำ Unit Test ที่ใช้เครื่องมือวัด ตรวจสอบว่าคุณใช้ 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() การแทนที่การเชื่อมโยงใน 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 ด้วยของปลอมได้โดยใช้ @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 ที่สร้างขึ้นให้ทำงานในการทดสอบการวัดคุมหรือการทดสอบ Robolectric ตามที่อธิบายไว้ในแอปพลิเคชันทดสอบ

ออบเจ็กต์ 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

แหล่งข้อมูลเพิ่มเติม

ดูข้อมูลเพิ่มเติมเกี่ยวกับการทดสอบได้ที่แหล่งข้อมูลเพิ่มเติมต่อไปนี้

เอกสารประกอบ

ดูเนื้อหา