0

I have a fragment whose instance of ViewModel is provided by a component, scoped to the navigation graph of Main Activity. The Dagger app component was injected into the single Main Activity and grabbed inside the the fragment as a lazy delegated variable.

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)
    }

The ViewModelProvider.Factory parameter of ViewModelProvider is using a create method from a Factory interface defined in MyViewModel class; this interface is annotated with @AssistedFactory whereas the savedStateHandle is @AssistInject annotated in the MyViewModel constructor.

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

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

    //Other ViewModel code 
}

App runs fine with the Dagger app component getting my provided instances of MyViewModel through its lazy delegates, but now when I'm trying to write an instrumented test to test some ui fields on the Fragment itself, I'm getting null point exception. Specifically:

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)

This tells me that the owner parameter, findNavController().getBackStackEntry(R.id.nav_graph) is failing with NPE, but I don't know how to go about resolving it so my test can compile and execute.

Here is my test code so far, please help where I'm missing here, else I might try removing Dagger and instantiating everything just to get testing going, which sounds backwards in retrospect. I'm not even sure if I'm writing this test scenario correctly, as I'm trying a hodge podge of solutions spread through various help forums and this site for solutions to my compile time issues and failure to build.

@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)
                    }
                }
            }
        }
    }

Any help or guidance in the right direction would be greatly appreciated as I've been stuck for a while here.

1 Answer 1

0

Answering my own question with how I got it working: Use TestNavHostController.

@Test
fun setUp() {
        mockNavController = TestNavHostController(ApplicationProvider.getApplicationContext())
        scenario = launchFragmentInContainer<MyFragment> {
            MyFragment().also { fragment ->
                fragment.viewLifecycleOwnerLiveData.observeForever { viewLifecycleOwner ->
                    if (viewLifecycleOwner != null) {
                        // The fragment’s view has just been created

                        fragment.requireActivity().runOnUiThread {
                            mockNavController.setViewModelStore(ViewModelStore())
                            mockNavController.setGraph(R.navigation.base_nav_graph)
                            Navigation.setViewNavController(fragment.requireView(), mockNavController)
                        }
                    }
                }
            }
        }

With this I got past the issues of instantiating my View Models.

Sign up to request clarification or add additional context in comments.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.