11、导航测试:NavHost测试、路由跳转验证、返回栈测试
导航测试,说白了就是验证你的App页面跳转对不对。
我见过太多项目,功能测试都过了,结果上线后发现某个深层页面回退时直接闪退。嗯,这就是导航测试没做到位。
今天我们就来聊聊Compose里的导航测试怎么做。我会从NavHost测试讲起,再到路由跳转验证,最后说返回栈测试。这些都是我在实际项目中踩过的坑,希望能帮你少走弯路。
11.1 NavHost测试基础
先说说NavHost怎么测。你想想看,NavHost是整个导航系统的核心,它决定了哪个页面该显示。测试它,其实就是验证路由和页面的对应关系。
我习惯用这种方式写NavHost:
// 定义路由常量
object Routes {
const val HOME = "home"
const val DETAIL = "detail/{id}"
const val SETTINGS = "settings"
}
// 导航图
@Composable
fun AppNavHost(
navController: NavHostController,
modifier: Modifier = Modifier
) {
NavHost(
navController = navController,
startDestination = Routes.HOME,
modifier = modifier
) {
composable(Routes.HOME) {
HomeScreen(
onNavigateToDetail = { id ->
navController.navigate("detail/$id")
}
)
}
composable(
route = Routes.DETAIL,
arguments = listOf(
navArgument("id") { type = NavType.StringType }
)
) { backStackEntry ->
val id = backStackEntry.arguments?.getString("id") ?: ""
DetailScreen(
id = id,
onBack = { navController.popBackStack() }
)
}
composable(Routes.SETTINGS) {
SettingsScreen(
onBack = { navController.popBackStack() }
)
}
}
}
测试时,我们需要创建一个NavController,然后渲染NavHost。这里有个关键点——要用TestNavHostController。
@Test
fun testNavHost_rendersHomeScreen() {
// 创建测试用的NavController
val navController = TestNavHostController(LocalContext.current)
// 设置Compose测试规则
composeTestRule.setContent {
AppNavHost(navController = navController)
}
// 验证首页内容显示
composeTestRule.onNodeWithText("首页").assertIsDisplayed()
composeTestRule.onNodeWithText("进入详情").assertIsDisplayed()
}
11.2 路由跳转验证
路由跳转验证,说白了就是检查点击按钮后,页面有没有正确跳转。
我曾在项目中遇到过一个问题:某个按钮点击后,页面跳转了,但参数传错了。结果详情页一直显示空数据,排查了半天才发现是路由参数没拼对。
所以,验证路由跳转要关注两点:
- 跳转是否发生——检查当前路由是否正确
- 参数是否传递正确——检查路由中的参数值
来看一个完整的测试示例:
@Test
fun testNavigateToDetail_withCorrectId() {
val navController = TestNavHostController(LocalContext.current)
composeTestRule.setContent {
AppNavHost(navController = navController)
}
// 点击"进入详情"按钮
composeTestRule.onNodeWithText("进入详情").performClick()
// 验证路由是否正确
val route = navController.currentBackStackEntry?.destination?.route
assertEquals("detail/{id}", route)
// 验证参数是否正确
val arguments = navController.currentBackStackEntry?.arguments
assertEquals("123", arguments?.getString("id"))
}
navController.currentBackStackEntry可以获取当前路由和参数。这是验证跳转是否正确的标准做法。
还有一种情况——深层链接跳转。比如从通知栏点击后,直接跳转到某个详情页。这种跳转也要测:
@Test
fun testDeepLinkNavigation() {
val navController = TestNavHostController(LocalContext.current)
composeTestRule.setContent {
AppNavHost(navController = navController)
}
// 模拟深层链接跳转
navController.navigate("detail/456")
// 验证跳转结果
composeTestRule.onNodeWithText("详情页").assertIsDisplayed()
composeTestRule.onNodeWithText("ID: 456").assertIsDisplayed()
}
11.3 返回栈测试
返回栈测试,这是最容易出问题的地方。
我曾经遇到过一个线上bug:用户从首页进入详情页,再从详情页进入编辑页,然后点击返回,结果直接回到了首页。按理说应该先回到详情页才对。这就是返回栈管理出了问题。
返回栈测试主要验证三点:
- 正常返回——点击返回按钮,回到上一页
- 返回栈清空——某些场景下需要清空返回栈(比如登录后)
- 多次返回——连续返回多个页面,验证栈的正确性
来看一个返回栈测试的例子:
@Test
fun testBackStack_popToPreviousScreen() {
val navController = TestNavHostController(LocalContext.current)
composeTestRule.setContent {
AppNavHost(navController = navController)
}
// 先跳转到详情页
composeTestRule.onNodeWithText("进入详情").performClick()
composeTestRule.onNodeWithText("详情页").assertIsDisplayed()
// 再跳转到编辑页
composeTestRule.onNodeWithText("编辑").performClick()
composeTestRule.onNodeWithText("编辑页").assertIsDisplayed()
// 点击返回
composeTestRule.onNodeWithText("返回").performClick()
// 验证回到了详情页,而不是首页
composeTestRule.onNodeWithText("详情页").assertIsDisplayed()
composeTestRule.onNodeWithText("首页").assertDoesNotExist()
}
还有一种常见场景——登录后清空返回栈。比如用户从首页进入登录页,登录成功后应该直接跳转到主页,并且不能返回到登录页:
@Test
fun testLogin_clearsBackStack() {
val navController = TestNavHostController(LocalContext.current)
composeTestRule.setContent {
AppNavHost(navController = navController)
}
// 模拟从首页跳转到登录页
composeTestRule.onNodeWithText("登录").performClick()
composeTestRule.onNodeWithText("登录页").assertIsDisplayed()
// 模拟登录成功
composeTestRule.onNodeWithText("登录成功").performClick()
// 验证跳转到主页
composeTestRule.onNodeWithText("主页").assertIsDisplayed()
// 验证返回栈被清空(按返回键不会回到登录页)
composeTestRule.onNodeWithText("返回").performClick()
composeTestRule.onNodeWithText("主页").assertIsDisplayed()
}
11.4 导航测试的SVG流程图
下面这张图展示了导航测试的核心流程:
11.5 常见问题与避坑指南
最后,分享几个我在项目中遇到的坑:
- 忘记设置startDestination——NavHost必须指定startDestination,否则会崩溃。测试时也要确保这个值正确。
- 参数类型不匹配——路由中定义的是String,但传了Int。测试时一定要验证参数类型。
- 返回栈溢出——连续跳转太多页面,没有及时清理返回栈。建议在测试中模拟多次跳转,验证栈的深度。
导航测试看似简单,但实际做起来有很多细节。记住一个原则:用户能走到的页面,测试都要覆盖到。特别是返回栈,这是用户最常操作的功能,也是最容易出问题的地方。
好了,导航测试就聊到这里。希望这些经验对你有帮助。
公众号:蓝海资料掘金营,微信deep3321