测试导航组件:"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)
        }