Hướng dẫn kiểm thử Hilt

Một trong những lợi ích của việc sử dụng các khung chèn phần phụ thuộc như Hilt là giúp bạn kiểm thử mã dễ dàng hơn.

Kiểm thử đơn vị

Bạn không cần dùng Hilt để kiểm thử đơn vị vì khi kiểm thử một lớp sử dụng tính năng chèn hàm khởi tạo, bạn không cần phải sử dụng Hilt để tạo bản sao của lớp đó. Thay vào đó, bạn có thể trực tiếp gọi một hàm khởi tạo lớp bằng cách truyền vào các phần phụ thuộc giả hoặc mô phỏng, giống như cách bạn sẽ làm nếu hàm khởi tạo không được chú thích:

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

Điều này cũng áp dụng cho các lớp ViewModel thu được bằng cách gọi hiltViewModel() trong các thành phần kết hợp. Trong các kiểm thử đơn vị, hãy tạo ViewModel trực tiếp bằng các đối tượng giả. Để biết thông tin về cách trạng thái chuyển từ ViewModel vào các thành phần kết hợp, hãy xem bài viết Trạng thái và Jetpack Compose cũng như Vị trí chuyển trạng thái lên trên.

Kiểm thử toàn diện

Đối với thử nghiệm tích hợp, Hilt sẽ chèn các phần phụ thuộc giống như trong mã sản xuất. Việc sử dụng Hilt để kiểm thử không yêu cầu bảo trì vì Hilt tự động tạo một tập hợp các thành phần mới cho mỗi kiểm thử.

Thêm các phần phụ thuộc kiểm thử

Để sử dụng Hilt trong các kiểm thử, hãy đưa phần phụ thuộc hilt-android-testing vào dự án:

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")

}

Thiết lập thử nghiệm giao diện người dùng

Bạn phải chú thích mọi kiểm thử giao diện người dùng sử dụng Hilt với @HiltAndroidTest. Chú thích này chịu trách nhiệm tạo các thành phần Hilt cho mỗi lần kiểm thử.

Ngoài ra, bạn cần thêm HiltAndroidRule vào lớp kiểm thử. Công cụ này quản lý trạng thái của các thành phần và được dùng để thực hiện thao tác chèn trong kiểm thử:

@HiltAndroidTest
class SettingsScreenTest {

    @get:Rule(order = 0)
    val hiltRule = HiltAndroidRule(this)

    @get:Rule(order = 1)
    val composeRule = createAndroidComposeRule<HiltTestActivity>()

    // Compose UI tests here.
}

Tiếp theo, kiểm thử cần được biết về lớp Application mà Hilt tự động tạo cho bạn.

Để cho phép Hilt chèn các phần phụ thuộc, bạn phải tạo một hoạt động trống có tên là HiltTestActivity trong nhóm tài nguyên androidTest và chú thích hoạt động đó bằng @AndroidEntryPoint. Sau đó, createAndroidComposeRule sẽ dùng hoạt động này làm máy chủ lưu trữ cho nội dung có khả năng kết hợp của bạn.

Kiểm thử ứng dụng

Bạn phải thực thi các kiểm thử đo lường sử dụng Hilt trong đối tượng Application để hỗ trợ Hilt. Thư viện này cung cấp HiltTestApplication để sử dụng trong kiểm thử. Nếu chương trình kiểm thử của bạn cần một ứng dụng cơ sở khác, vui lòng xem Tùy chỉnh ứng dụng để kiểm thử.

Bạn phải thiết lập ứng dụng kiểm thử để chạy trong các kiểm thử đo lường hoặc kiểm thử Robolectric. Các hướng dẫn sau đây không dành riêng cho Hilt, nhưng là hướng dẫn chung về cách chỉ định một ứng dụng tuỳ chỉnh để chạy trong các lần kiểm thử.

Đặt ứng dụng kiểm thử trong các kiểm thử đo lường

Để sử dụng ứng dụng kiểm thử Hilt trong kiểm thử đo lường, bạn cần định cấu hình một trình chạy kiểm thử mới. Điều này giúp Hilt hoạt động cho tất cả các kiểm thử đo lường trong dự án của bạn. Thực hiện các bước sau đây:

  1. Tạo một lớp tuỳ chỉnh giúp mở rộng AndroidJUnitRunner trong thư mục androidTest.
  2. Ghi đè hàm newApplication và truyền tên của ứng dụng kiểm thử Hilt được tạo.
// 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)
    }
}

Tiếp theo, hãy định cấu hình trình chạy kiểm thử này trong tệp Gradle như được mô tả ở phần hướng dẫn kiểm thử đơn vị đo lường. Hãy đảm bảo là bạn sử dụng đường dẫn lớp đầy đủ:

android {
    defaultConfig {
        // Replace com.example.android.dagger with your class path.
        testInstrumentationRunner = "com.example.android.dagger.CustomTestRunner"
    }
}
Đặt ứng dụng kiểm thử trong các kiểm thử Robolectric

Nếu sử dụng Robolectric để kiểm thử lớp giao diện người dùng, bạn có thể chỉ định ứng dụng nào sẽ sử dụng trong tệp robolectric.properties:

application = dagger.hilt.android.testing.HiltTestApplication

Ngoài ra, bạn có thể định cấu hình ứng dụng cho từng lần kiểm thử riêng lẻ bằng cách sử dụng chú thích @Config của Robolectric:

@HiltAndroidTest
@Config(application = HiltTestApplication::class)
class SettingsScreenTest {

  @get:Rule
  var hiltRule = HiltAndroidRule(this)

  // Robolectric tests here.
}

Các tính năng thử nghiệm

Khi Hilt đã sẵn sàng để sử dụng trong quá trình kiểm thử, bạn có thể dùng một số tính năng để tuỳ chỉnh quy trình kiểm thử.

Chèn các loại trong kiểm thử

Để chèn các loại vào một thử nghiệm, hãy sử dụng @Inject để chèn trường. Để yêu cầu Hilt điền vào các trường @Inject, hãy gọi hiltRule.inject().

Hãy xem ví dụ sau về kiểm thử đo lường:

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

Thay thế một liên kết

Nếu cần chèn một bản sao giả mạo hoặc mô phỏng của một phần phụ thuộc, bạn cần yêu cầu Hilt không sử dụng liên kết mà nó đã sử dụng trong mã phát hành chính thức, đồng thời sử dụng một liên kết khác. Để thay thế một liên kết, bạn cần thay thế mô-đun chứa liên kết bằng một mô-đun kiểm thử có chứa các liên kết mà bạn muốn sử dụng trong quá trình kiểm thử.

Ví dụ, giả sử mã phát hành chính thức khai báo một liên kết cho AnalyticsService như sau:

@Module
@InstallIn(SingletonComponent::class)
abstract class AnalyticsModule {

  @Singleton
  @Binds
  abstract fun bindAnalyticsService(
    analyticsServiceImpl: AnalyticsServiceImpl
  ): AnalyticsService
}

Để thay thế liên kết AnalyticsService trong các kiểm thử, hãy tạo một mô-đun Hilt mới trong thư mục test hoặc androidTest bằng phần phụ thuộc giả mạo rồi chú thích bằng @TestInstallIn. Thay vào đó, tất cả kiểm thử trong thư mục đó sẽ được chèn phần phụ thuộc giả mạo.

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

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

Vì các thành phần kết hợp thường sử dụng gián tiếp các phần phụ thuộc này thông qua một ViewModel thu được bằng hiltViewModel(), nên việc thay thế mối liên kết trong Hilt là đủ. Thành phần kết hợp đang được kiểm thử sẽ tự động chọn thành phần giả.

Thay thế liên kết trong một kiểm thử duy nhất

Để thay thế một liên kết trong một kiểm thử đơn thay vì mọi kiểm thử, hãy gỡ cài đặt mô-đun Hilt của một kiểm thử bằng cách sử dụng chú thích @UninstallModules, đồng thời tạo một mô-đun kiểm thử mới bên trong kiểm thử đó.

Theo ví dụ về AnalyticsService từ phiên bản trước, hãy bắt đầu bằng cách yêu cầu Hilt bỏ qua mô-đun phát hành chính thức bằng cách sử dụng chú thích @UninstallModules trong lớp kiểm thử:

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsScreenTest { ... }

Tiếp theo, bạn phải thay thế liên kết. Tạo một mô-đun mới trong lớp kiểm thử xác định liên kết kiểm thử:

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsScreenTest {

  @Module
  @InstallIn(SingletonComponent::class)
  abstract class TestModule {

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

  // ...
}

Thao tác này chỉ thay thế liên kết cho một lớp kiểm thử duy nhất. Nếu bạn muốn thay thế liên kết cho tất cả các lớp kiểm thử, hãy sử dụng chú thích @TestInstallIn của phần trên. Ngoài ra, bạn có thể đặt liên kết kiểm thử trong mô-đun test dành cho kiểm thử Robolectric, hoặc trong mô-đun androidTest đối với kiểm thử đo lường. Bạn nên sử dụng @TestInstallIn bất cứ khi nào có thể.

Liên kết các giá trị mới

Sử dụng chú thích @BindValue để dễ dàng liên kết các trường trong kiểm thử vào biểu đồ phần phụ thuộc Hilt. Chú thích một trường bằng @BindValue và trường này sẽ được liên kết với loại trường đã khai báo bằng bất kỳ bộ hạn định nào có trong trường đó.

Trong ví dụ AnalyticsService, bạn có thể thay thế AnalyticsService bằng một nội dung giả bằng cách sử dụng @BindValue:

@UninstallModules(AnalyticsModule::class)
@HiltAndroidTest
class SettingsScreenTest {

  @BindValue @JvmField
  val analyticsService: AnalyticsService = FakeAnalyticsService()

  ...
}

Việc này giúp đơn giản hoá cả việc thay thế lẫn tham chiếu một liên kết trong quá trình kiểm thử bằng cách cho phép bạn thực hiện cả hai cùng lúc.

@BindValue hoạt động với bộ hạn định và các chú thích kiểm thử khác. Chẳng hạn nếu sử dụng các thư viện kiểm thử như Mockito, bạn có thể sử dụng các thư viện đó trong kiểm thử Robolectric như sau:

...
class SettingsScreenTest {
  ...

  @BindValue @ExampleQualifier @Mock
  lateinit var qualifiedVariable: ExampleCustomType

  // Robolectric tests here
}

Nếu cần thêm nhiều liên kết, bạn có thể sử dụng chú thích @BindValueIntoSet@BindValueIntoMap thay cho @BindValue. @BindValueIntoMap cũng yêu cầu bạn chú thích trường bằng chú thích khóa bản đồ.

Các trường hợp đặc biệt

Hilt cũng cung cấp các tính năng để hỗ trợ các trường hợp sử dụng không theo chuẩn.

Tùy chỉnh ứng dụng để thử nghiệm

Nếu bạn không thể dùng HiltTestApplication vì ứng dụng kiểm thử cần mở rộng một ứng dụng khác, hãy chú thích một lớp hoặc giao diện mới bằng @CustomTestApplication, truyền giá trị của lớp cơ sở mà bạn muốn ứng dụng Hilt đã tạo sẽ mở rộng.

@CustomTestApplication sẽ tạo một lớp Application sẵn sàng để thử nghiệm với Hilt, lớp này sẽ mở rộng ứng dụng mà bạn đã truyền dưới dạng tham số.

@CustomTestApplication(BaseApplication::class)
interface HiltTestApplication

Trong ví dụ này, Hilt sẽ tạo một Application có tên là HiltTestApplication_Application mở rộng lớp BaseApplication. Nói chung, tên của ứng dụng đã tạo là tên của lớp chú thích được thêm vào _Application. Bạn phải thiết lập ứng dụng kiểm thử Hilt đã tạo để chạy trongkiểm thử đo lường hoặc Kiểm thử Robolectric như được mô tả trong phần Kiểm thử ứng dụng.

Nhiều đối tượng TestRule trong kiểm thử đo lường

Các quy trình kiểm thử giao diện người dùng Compose đã kết hợp HiltAndroidRule với một quy tắc kiểm thử Compose, chẳng hạn như createAndroidComposeRule. Nếu bạn có thêm các đối tượng TestRule, hãy đảm bảo HiltAndroidRule chạy trước. Khai báo thứ tự thực thi bằng thuộc tính order trên @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.
}

Ngoài ra, bạn có thể gói các quy tắc bằng RuleChain, đặt HiltAndroidRule làm quy tắc bên ngoài.

@HiltAndroidTest
class SettingsScreenTest {

  @get:Rule
  var rule = RuleChain.outerRule(HiltAndroidRule(this)).
        around(SettingsScreenTestRule(...))

  // UI tests here.
}

Sử dụng một điểm truy cập trước khi có thể dùng thành phần singleton

Chú thích @EarlyEntryPoint cung cấp một giải pháp khi cần tạo một điểm truy cập Hilt trước khi thành phần singleton có trong kiểm thử Hilt.

Vui lòng tìm hiểu thêm thông tin về @EarlyEntryPoint trong tài liệu về Hilt.

Tài nguyên khác

Để tìm hiểu thêm về quy trình kiểm thử, hãy xem thêm các tài nguyên sau đây:

Tài liệu

Xem nội dung