Search code examples
androidandroid-fragmentsandroid-testing

Is there away to mock ViewModel that is inside of a fragment


Is there a way to mock ViewModel that's built is inside of a fragment? I'm trying to run some tests on a fragment, one of the fragment functions interacts with the ViewModel, I would like to run the test function and provided a mocked result for the ViewModel. Is this even possilbe?

MyFragment

class MyFragment : Fragment() {
    @Inject
    lateinit var viewModel: MyViewModel
    
    override fun onCreate(savedInstanceState: Bundle?) {
        (requireActivity().application as MyApplication).appComponent.inject(this)
        super.onCreate(savedInstanceState)
    }
    
} 

Test

@RunWith(RoboeltricTestRunner::) {
    
    @Before
    fun setup() {
        FragmentScenario.Companion.launchIncontainer(MyFragment::class.java)
    }
}

Solution

  • I thought I would post this for anyone else struggling to find a solution. You'll want to use a Fragment Factory, that has a dependency on the ViewModel. Injecting the ViewModel into the fragments constructor allows the ViewModel to easliy be mocked. There are a few steps that need to be completed for a FragmentFactory but it's not that complicated once you do a couple of them.

    1. Fragment Add the ViewModel into the constructor.
    class MyFragment(private val viewModel: ViewModel) : Fragment {
    
        ... 
    }
    
    1. FragmentFactory, allows fragments to have dependencies in the constructor.
    class MyFragmentFactory(private val viewModel: MyViewModel) : FragmentFactory() {
        override fun instantiate(classLoader: ClassLoader, className: String): Fragment {
            return when(className) {
    
                MyFirstFragment::class.java.name -> {
                    MyFragment(viewModel)
                }
    
                // You could use this factory for multiple Fragments.
                MySecondFragment::class.java.name -> {
                    MySecondFragment(viewModel)
                }
    
                // You also don't have to pass the dependency
                MyThirdFragment::class.java.name -> {
                    MyThirdFragment()
                }
                else -> super.instantiate(classLoader, className)
            }
        }
    }
    
    1. Main Activity
    class MainActivity : AppCompatActivity() {
        override fun onCreate(savedInstanceState: Bundle?) {
            // Create your ViewModel
            val viewModel = ViewModelProvider(this).get(MainViewModel::class.java)
    
            // create the FragmentFactory and the viewModel dependency.
            supportFragmentManager.fragmentFactory = MainFragmentFactory(viewModel)
    
            // FragmentFactory needs to be created before super in an activity. 
            super.onCreate(savedInstanceState)
    
        }
    }
    
    1. Test
    @RunWith(RobolectricTestRunner::class)
    class MyFragmentUnitTest {
        
        @Before 
        fun setup() { 
            val viewModel: MainViewModel = mock(MyViewModel::class.java)
            ...
        }
    }