升级fragment-testing到1.5.3后,Spy Fragment无法访问ViewModel报错
问题场景
升级androidx.fragment:fragment-testing从1.4.x版本到1.5.3后,使用Mockito spy包装的Fragment在Robolectric测试中出现以下错误:
Can't access ViewModels from detached fragment at androidx.fragment.app.Fragment.getViewModelStore for spy(fragment) object
- 普通Fragment实例的测试可正常通过,仅Spy Fragment触发该错误
- 切换到FragmentScenario测试同样报错
- 问题仅出现在Robolectric + Mockito的测试环境中
- 测试代码示例:
activity = Robolectric.buildActivity(AppCompatActivity::class.java).setup().get() activity.supportFragmentManager.beginTransaction().add(android.R.id.content, fragment).commit() if (idleLooper) { shadowOf(getMainLooper()).idle() }
原因分析
Fragment 1.5.x版本对生命周期状态的校验逻辑做了强化,而Mockito默认的spy代理会拦截Fragment的所有方法调用,包括isDetached()这类判断生命周期状态的方法。即使Fragment已经通过Transaction添加并完成commit,spy的代理可能没有正确传递真实Fragment的状态,导致框架误判Fragment仍处于detached状态,进而在访问ViewModelStore时抛出异常。
解决方案
方案1:创建Spy时指定调用真实方法
使用Mockito的CALLS_REAL_METHODS作为默认答案,确保生命周期状态相关方法调用真实Fragment的实现,避免状态判断出错:
val fragment = spy( MyFragment(contact, mockViewModel, mockFragmentManager), withSettings().defaultAnswer(CALLS_REAL_METHODS) )
方案2:手动存根状态判断方法
如果不需要所有方法都调用真实实现,可以单独存根isDetached()方法,让它返回正确的状态:
val fragment = spy(MyFragment(contact, mockViewModel, mockFragmentManager)) `when`(fragment.isDetached()).thenReturn(false)
方案3:改用FragmentFactory注入依赖
Fragment 1.5+推荐使用FragmentFactory来注入依赖,避免直接通过构造函数传入Mock的FragmentManager,减少状态冲突的概率:
// 自定义测试用FragmentFactory class TestFragmentFactory( private val contact: Contact, private val viewModel: MyViewModel ) : FragmentFactory() { override fun instantiate(classLoader: ClassLoader, className: String): Fragment { return when (className) { MyFragment::class.java.name -> MyFragment(contact, viewModel) else -> super.instantiate(classLoader, className) } } } // 测试中使用FragmentFactory启动Fragment val scenario = FragmentScenario.launchInContainer( MyFragment::class.java, null, TestFragmentFactory(contact, mockViewModel) )
验证
以上任意方案都可以让Spy Fragment在1.5.3版本的测试中正常访问ViewModelStore,恢复和1.4.x版本一致的测试行为。
内容的提问来源于stack exchange,提问作者Ankit Jangid

