测试导航组件:"does not have a NavController"
Testing Navigation component: "does not have a NavController"
我正在实施 Espresso 测试。我正在使用 NavGraph
范围 ViewModel
的片段。问题是当我尝试测试 Fragment
时,我得到了 IllegalStateException
,因为 Fragment
没有 NavController
集。我该如何解决这个问题?
class MyFragment : Fragment(), Injectable {
private val viewModel by navGraphViewModels<MyViewModel>(R.id.scoped_graph){
viewModelFactory
}
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
//Other stuff
}
测试class:
class FragmentTest {
class TestMyFragment: MyFragment(){
val navMock = mock<NavController>()
override fun getNavController(): NavController {
return navMock
}
}
@Mock
private lateinit var viewModel: MyViewModel
private lateinit var scenario: FragmentScenario<TestMyFragment>
@Before
fun prepareTest(){
MockitoAnnotations.initMocks(this)
scenario = launchFragmentInContainer<TestMyFragment>(themeResId = R.style.Theme_AppCompat){
TestMyFragment().apply {
viewModelFactory = ViewModelUtil.createFor(viewModel)
}
}
// My test
}
我遇到的异常:
java.lang.IllegalStateException: View android.widget.ScrollView does not have a NavController setjava.lang.IllegalStateException
正如在 docs 中所见,这是建议的方法:
// Create a mock NavController
val mockNavController = mock(NavController::class.java)
scenario = launchFragmentInContainer<TestMyFragment>(themeResId = R.style.Theme_AppCompat) {
TestMyFragment().also { fragment ->
// In addition to returning a new instance of our Fragment,
// get a callback whenever the fragment’s view is created
// or destroyed so that we can set the mock NavController
fragment.viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner ->
if (viewLifecycleOwner != null) {
// The fragment’s view has just been created
Navigation.setViewNavController(fragment.requireView(), mockNavController)
}
}
}
}
此后您可以像这样对模拟的 mockNavController
执行验证:
verify(mockNavController).navigate(SearchFragmentDirections.showRepo("foo", "bar"))
参考architecture components sample。
docs 中也提到了另一种方法:
// Create a graphical FragmentScenario for the TitleScreen
val titleScenario = launchFragmentInContainer<TitleScreen>()
// Set the NavController property on the fragment
titleScenario.onFragment { fragment ->
Navigation.setViewNavController(fragment.requireView(), mockNavController)
}
如果在 onViewCreated()
(包括)之前与 NavController
发生交互,则此方法将不起作用。使用这种方法 onFragment()
会在生命周期中设置 mock NavController
太晚,导致 findNavController()
调用失败。作为适用于所有情况的统一方法,我建议使用第一种方法。
您缺少设置 NavController
:
testFragmentScenario.onFragment {
Navigation.setViewNavController(it.requireView(), mockNavController)
}
我正在实施 Espresso 测试。我正在使用 NavGraph
范围 ViewModel
的片段。问题是当我尝试测试 Fragment
时,我得到了 IllegalStateException
,因为 Fragment
没有 NavController
集。我该如何解决这个问题?
class MyFragment : Fragment(), Injectable {
private val viewModel by navGraphViewModels<MyViewModel>(R.id.scoped_graph){
viewModelFactory
}
@Inject
lateinit var viewModelFactory: ViewModelProvider.Factory
//Other stuff
}
测试class:
class FragmentTest {
class TestMyFragment: MyFragment(){
val navMock = mock<NavController>()
override fun getNavController(): NavController {
return navMock
}
}
@Mock
private lateinit var viewModel: MyViewModel
private lateinit var scenario: FragmentScenario<TestMyFragment>
@Before
fun prepareTest(){
MockitoAnnotations.initMocks(this)
scenario = launchFragmentInContainer<TestMyFragment>(themeResId = R.style.Theme_AppCompat){
TestMyFragment().apply {
viewModelFactory = ViewModelUtil.createFor(viewModel)
}
}
// My test
}
我遇到的异常:
java.lang.IllegalStateException: View android.widget.ScrollView does not have a NavController setjava.lang.IllegalStateException
正如在 docs 中所见,这是建议的方法:
// Create a mock NavController
val mockNavController = mock(NavController::class.java)
scenario = launchFragmentInContainer<TestMyFragment>(themeResId = R.style.Theme_AppCompat) {
TestMyFragment().also { fragment ->
// In addition to returning a new instance of our Fragment,
// get a callback whenever the fragment’s view is created
// or destroyed so that we can set the mock NavController
fragment.viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner ->
if (viewLifecycleOwner != null) {
// The fragment’s view has just been created
Navigation.setViewNavController(fragment.requireView(), mockNavController)
}
}
}
}
此后您可以像这样对模拟的 mockNavController
执行验证:
verify(mockNavController).navigate(SearchFragmentDirections.showRepo("foo", "bar"))
参考architecture components sample。
docs 中也提到了另一种方法:
// Create a graphical FragmentScenario for the TitleScreen
val titleScenario = launchFragmentInContainer<TitleScreen>()
// Set the NavController property on the fragment
titleScenario.onFragment { fragment ->
Navigation.setViewNavController(fragment.requireView(), mockNavController)
}
如果在 onViewCreated()
(包括)之前与 NavController
发生交互,则此方法将不起作用。使用这种方法 onFragment()
会在生命周期中设置 mock NavController
太晚,导致 findNavController()
调用失败。作为适用于所有情况的统一方法,我建议使用第一种方法。
您缺少设置 NavController
:
testFragmentScenario.onFragment {
Navigation.setViewNavController(it.requireView(), mockNavController)
}