You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Android测试:Dagger注入ViewModel的Fragment集成测试报空指针异常

问题:Fragment集成测试中ViewModelProvider的owner参数空指针异常

现有实现

Fragment中获取ViewModel的代码

Fragment通过lazy委托从导航图的ViewModelStore中获取实例:

private val myViewModel: MyViewModel by lazy {
        ViewModelProvider(findNavController().getBackStackEntry(R.id.nav_graph), Factory(this) { stateHandle ->
            (activity as MainActivity).myComponent.myViewModel()
                .create(stateHandle)}).get(MyViewModel::class.java)
    }

ViewModel的实现

ViewModel使用@AssistedInject和@AssistedFactory处理SavedStateHandle:

class MyViewModel @AssistedInject constructor(
    private val myRepository: MyRepository,
    @Assisted val savedStateHandle: SavedStateHandle
    ) : ViewModel() {

    @AssistedFactory
    interface MyViewModelFactory : ViewModelProvider.Factory {
        fun create(savedStateHandle: SavedStateHandle) : MyViewModel
    }

    // 其他ViewModel代码
}

测试报错信息

应用运行正常,但集成测试时抛出空指针异常:

java.lang.NullPointerException: Parameter specified as non-null is null: method androidx.lifecycle.ViewModelProvider.<init>, parameter owner
at androidx.lifecycle.ViewModelProvider.<init>(Unknown Source:2)

原因是findNavController().getBackStackEntry(R.id.nav_graph)返回null,导致ViewModelProvider的owner参数为空。

当前测试代码

@RunWith(AndroidJUnit4::class)
class MyFragmentTest {

    @get:Rule
    var activityRule = ActivityScenarioRule(MainActivity::class.java)

    private lateinit var scenario: FragmentScenario<MyFragment>
    private lateinit var binding: FragmentMyBinding
    private lateinit var mockNavController : NavController
    
    @Test
    fun setUp() {
        mockNavController = mock(NavController::class.java)
        scenario = launchFragmentInContainer<MyFragment> {
            MyFragment().also { fragment ->
                fragment.viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner ->
                    if (viewLifecycleOwner != null) {
                        // The fragment’s view has just been created
                        mockNavController.setGraph(R.navigation.nav_graph)
                        Navigation.setViewNavController(fragment.requireView(), mockNavController)
                    }
                }
            }
        }
    }

解决方案

1. 修正测试方法的生命周期注解

setUp是JUnit的初始化方法,不能用@Test标注,应该替换为@Before,并单独编写测试方法。

2. 模拟有效的BackStackEntry

当前mock的NavController没有返回有效的BackStackEntry,这是导致NPE的核心原因。需要mockSavedStateViewModelStoreOwner(BackStackEntry的父接口),并让NavController返回该实例。

3. 注入Mock ViewModel

测试中无需依赖真实的Dagger组件,直接提供mock的ViewModel实例,隔离底层依赖。

修改后的测试代码示例:

@RunWith(AndroidJUnit4::class)
class MyFragmentTest {

    private lateinit var mockNavController: NavController
    private lateinit var mockBackStackEntry: SavedStateViewModelStoreOwner
    private lateinit var mockViewModel: MyViewModel

    @Before
    fun setUp() {
        // 初始化所有mock对象
        mockNavController = mock(NavController::class.java)
        mockBackStackEntry = mock(SavedStateViewModelStoreOwner::class.java)
        mockViewModel = mock(MyViewModel::class.java)

        // 让NavController返回mock的BackStackEntry
        `when`(mockNavController.getBackStackEntry(R.id.nav_graph)).thenReturn(mockBackStackEntry)

        // 为mock的BackStackEntry提供有效的ViewModelStore
        val viewModelStore = ViewModelStore()
        `when`(mockBackStackEntry.viewModelStore).thenReturn(viewModelStore)
    }

    @Test
    fun testFragmentUIElements() {
        // 启动Fragment并设置导航控制器
        launchFragmentInContainer<MyFragment> {
            viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner ->
                if (viewLifecycleOwner != null) {
                    Navigation.setViewNavController(requireView(), mockNavController)
                    // 将mock的ViewModel注入到导航栈的ViewModelStore中
                    ViewModelProvider(mockBackStackEntry, object : ViewModelProvider.Factory {
                        override fun <T : ViewModel> create(modelClass: Class<T>): T {
                            return mockViewModel as T
                        }
                    }).get(MyViewModel::class.java)
                }
            }
        }

        // 示例:验证UI控件是否正常显示
        onView(withId(R.id.your_ui_element_id)).check(matches(isDisplayed()))
    }
}

关键说明

  • 生命周期修正:@Before注解的方法会在每个测试用例执行前初始化mock对象,确保测试环境一致。
  • BackStackEntry模拟:必须让getBackStackEntry返回非null实例,否则ViewModelProvider无法初始化。这里通过mockSavedStateViewModelStoreOwner并提供ViewModelStore解决。
  • Mock ViewModel注入:直接使用mock的ViewModel可以隔离Repository等依赖,让测试专注于UI逻辑验证,无需启动完整的Dagger组件。

内容的提问来源于stack exchange,提问作者MajinKenn

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 06:23:09