我将 Compose、Navigation、ViewModel 和 Hilt 与
SavedStateHandle
结合使用来访问 ViewModel 中的导航参数。一切都运行良好,但是在仪器测试方面我遇到了问题。
假设我有一个 ViewModel:
@HiltViewModel
class MyViewModel @Inject constructor(
application: Application,
...
savedStateHandle: SavedStateHandle,
) : AndroidViewModel(application) {
private val id = savedStateHandle.require<Long>("myId")
...
}
代表使用 ViewModel 的屏幕的可组合项:
@Composable
fun MyScreen(viewModel: MyViewModel = hiltViewModel()) {
...
}
使用 Hilt 呈现可组合项以提供 ViewModel 及其所有依赖项的仪器测试:
@HiltAndroidTest
class MyTest {
@get:Rule(order = 0)
val hiltRule by lazy { HiltAndroidRule(this) }
@get:Rule(order = 1)
val test by lazy { createAndroidComposeRule<TestActivity>() }
@Test
fun shouldRenderScreen() {
test.setContent { MyScreen() }
...
}
}
当我运行此测试时,应用程序将在
savedStateHandle.require<Long>("myId")
上崩溃,因为 myId
不存在。我无法找到访问或覆盖测试代码中的 SavedStateHandle
的方法。
在应用程序中,
MyScreen
被包裹在NavHost
中,这使我能够访问SavedStateHandle
中的导航参数:
@Composable
fun MyApp(
navController: NavHostController = rememberNavController(),
startDestination: String = "home",
) {
...
MyTheme {
NavHost(
navController = navController,
startDestination = startDestination,
) {
...
composable(
"route/{myId}", arguments = listOf(
navArgument("myId") { type = NavType.LongType }
)
) {
MyScreen()
}
...
}
}
}
现在我意识到,在测试中设置内容时,我可以使用
SavedStateHandle
实例化我自己的 ViewModel 并将其传递到屏幕中,但是我有很多由 Hilt 提供的依赖项,因此需要大量样板文件.
我尝试使用
@BindValue
在测试中提供 SavedStateHandle
,但这会导致重复的绑定错误。我已尝试在测试中使用 @Inject
,但未找到依赖项。最后,我放弃了测试单屏可组合项的设计,而是将测试内容设置为SavedStateHandle
通过
MyApp
。这不太理想,因为这个可组合项中还有其他我不想测试的代码。然而,令人惊讶的是,这与 startDestination="route/1"
中的 SavedStateHandle
中没有发现相同的论点问题。那么有没有一种简单的方法可以在测试中使用导航参数填充 Hilt 提供的 ViewModel
?
SavedStateHandle
从与 ViewModel 关联的
SavedStateHandle
的参数填充其参数 - 当在 NavHost 内部时,这是单独的目的地(这就是为什么带有 ViewModelStoreOwner
的路由填充 "route/{myId}"
参数 - 它如果您的路线是 myId
将不起作用,因为它不包含任何参数)。当您的测试中没有 "route/1"
时,它所提取的参数来自 Activity 本身 - 通过 Activity 的
NavHost
上的 extras
。因此,您可以在调用 intent
之前在 Activity 上设置参数,以便在调用
setContent
时它们可用:hiltViewModel()