Android Jetpack Compose导航测试:如何Mock ViewModel工厂?
问题描述
使用Kotlin开发Jetpack Compose应用时,在导航测试中需要Mock ViewModel以脱离API等外部资源依赖,但尝试多种方法无效,不清楚如何Mock ViewModel工厂。
现有代码如下:
ViewModel工厂代码
object AppViewModelProvider { val Factory = viewModelFactory { initializer { FavoriteViewModel(fieldFavoritesApplication().container.favoriteRepository) } //More initializers... } } fun CreationExtras.fieldFavoritesApplication(): FieldFavoritesApplication = (this[AndroidViewModelFactory.APPLICATION_KEY] as FieldFavoritesApplication)
导航宿主函数
fun FieldFavoritesNavHost( navController: NavHostController, modifier: Modifier = Modifier, favoriteViewModel: FavoriteViewModel = viewModel(factory = AppViewModelProvider.Factory) )
当前测试代码
class NavigationTest { @get:Rule val composeTestRule = createComposeRule() lateinit var navController:TestNavHostController @Before fun setupAppNavHost() { composeTestRule.setContent { navController = TestNavHostController(LocalContext.current) navController.navigatorProvider.addNavigator(ComposeNavigator()) FieldFavoritesApp(navController) } } @Test fun verifyStartDestination() { Assert.assertEquals(LeaguesDestination.route, navController.currentBackStackEntry?.destination?.route) } }
解决方案
方案1:直接传入Mock ViewModel(推荐,更简洁)
你的FieldFavoritesNavHost已经将favoriteViewModel作为可配置参数暴露,测试时无需Mock工厂,直接传入Mock实例即可:
- 添加MockK依赖(Kotlin项目常用Mock库)
在模块的build.gradle.kts中添加测试依赖:
testImplementation("io.mockk:mockk:1.13.8") androidTestImplementation("io.mockk:mockk-android:1.13.8")
- 修改测试代码,注入Mock ViewModel
import io.mockk.mockk import androidx.compose.ui.test.junit4.createComposeRule import androidx.navigation.testing.TestNavHostController import org.junit.Assert.assertEquals import org.junit.Before import org.junit.Rule import org.junit.Test class NavigationTest { @get:Rule val composeTestRule = createComposeRule() lateinit var navController: TestNavHostController // 创建宽松Mock的ViewModel(自动忽略未 stub 的方法调用) private val mockFavoriteViewModel: FavoriteViewModel = mockk(relaxed = true) @Before fun setupAppNavHost() { composeTestRule.setContent { navController = TestNavHostController(LocalContext.current) navController.navigatorProvider.addNavigator(ComposeNavigator()) // 直接渲染NavHost并传入Mock的ViewModel,跳过整个App层级更适合导航测试 FieldFavoritesNavHost( navController = navController, favoriteViewModel = mockFavoriteViewModel ) } } @Test fun verifyStartDestination() { assertEquals(LeaguesDestination.route, navController.currentBackStackEntry?.destination?.route) } }
方案2:Mock ViewModel工厂(适用于多ViewModel需统一Mock场景)
如果需要Mock多个ViewModel,可直接MockAppViewModelProvider.Factory:
- 创建Mock工厂并Stub初始化逻辑
import io.mockk.every import io.mockk.mockk class NavigationTest { @get:Rule val composeTestRule = createComposeRule() lateinit var navController: TestNavHostController private val mockFavoriteViewModel: FavoriteViewModel = mockk(relaxed = true) private val mockViewModelFactory: ViewModelProvider.Factory = mockk() @Before fun setupAppNavHost() { // Stub工厂的create方法,当请求FavoriteViewModel时返回Mock实例 every { mockViewModelFactory.create(FavoriteViewModel::class.java) } returns mockFavoriteViewModel // 其他ViewModel的Stub逻辑同理添加 composeTestRule.setContent { navController = TestNavHostController(LocalContext.current) navController.navigatorProvider.addNavigator(ComposeNavigator()) FieldFavoritesNavHost( navController = navController, favoriteViewModel = viewModel(factory = mockViewModelFactory) ) } } // ...测试方法 }
关键说明
- 优先选择方案1,因为直接传入Mock实例更直观,减少不必要的工厂Mock逻辑
- 使用
mockk(relaxed = true)可以避免为ViewModel的每个方法单独编写Stub,适合只关注导航流程的测试 - 导航测试建议直接渲染
FieldFavoritesNavHost而非整个App,减少无关组件的干扰
内容的提问来源于stack exchange,提问作者Milosz_p
相关产品推荐
相关产品推荐

