How To Correctly Mock Viewmodel On Androidtest
Solution 1:
Within your test setup you'll need to provide a test version of the FavoritesViewModelFactory which is being injected in the Fragment.
You could do something like the following, where the Module will need to be added to your TestAppComponent:
@Moduleobject TestFavoritesViewModelModule {
val viewModelFactory: FavoritesViewModelFactory = mock()
@JvmStatic@ProvidesfunprovideFavoritesViewModelFactory(): FavoritesViewModelFactory {
return viewModelFactory
}
}
You'd then be able to provide your Mock viewModel in the test.
funsetupViewModelFactory() {
whenever(TestFavoritesViewModelModule.viewModelFactory.create(FavoritesViewModel::class.java)).thenReturn(viewModel)
}
Solution 2:
I have solved this problem using an extra object injected by Dagger, you can find the full example here: https://github.com/fabioCollini/ArchitectureComponentsDemo
In the fragment I am not using directly the ViewModelFactory, I have defined a custom factory defined as a Dagger singleton: https://github.com/fabioCollini/ArchitectureComponentsDemo/blob/master/uisearch/src/main/java/it/codingjam/github/ui/search/SearchFragment.kt
Then in the test I replace using DaggerMock this custom factory using a factory that always returns a mock instead of the real viewModel: https://github.com/fabioCollini/ArchitectureComponentsDemo/blob/master/uisearchTest/src/androidTest/java/it/codingjam/github/ui/repo/SearchFragmentTest.kt
Solution 3:
Look like, you use kotlin and koin(1.0-beta). It is my decision for mocking
@RunWith(AndroidJUnit4::class)classDashboardFragmentTest : KoinTest {@Rule@JvmFieldval activityRule = ActivityTestRule(SingleFragmentActivity::class.java, true, true)
@Rule@JvmFieldval executorRule = TaskExecutorWithIdlingResourceRule()
@Rule@JvmFieldval countingAppExecutors = CountingAppExecutorsRule()
privateval testFragment = DashboardFragment()
privatelateinitvar dashboardViewModel: DashboardViewModel
privatelateinitvar router: Router
privateval devicesSuccess = MutableLiveData<List<Device>>()
privateval devicesFailure = MutableLiveData<String>()
@BeforefunsetUp() {
dashboardViewModel = Mockito.mock(DashboardViewModel::class.java)
Mockito.`when`(dashboardViewModel.devicesSuccess).thenReturn(devicesSuccess)
Mockito.`when`(dashboardViewModel.devicesFailure).thenReturn(devicesFailure)
Mockito.`when`(dashboardViewModel.getDevices()).thenAnswer { _ -> Any() }
router = Mockito.mock(Router::class.java)
Mockito.`when`(router.loginActivity(activityRule.activity)).thenAnswer { _ -> Any() }
StandAloneContext.loadKoinModules(hsApp + hsViewModel + api + listOf(module {
single(override = true) { router }
factory(override = true) { dashboardViewModel } bind ViewModel::class
}))
activityRule.activity.setFragment(testFragment)
EspressoTestUtil.disableProgressBarAnimations(activityRule)
}
@AfterfuntearDown() {
activityRule.finishActivity()
StandAloneContext.closeKoin()
}
@TestfundevicesSuccess(){
val list = listOf(Device(deviceName = "name1Item"), Device(deviceName = "name2"), Device(deviceName = "name3"))
devicesSuccess.postValue(list)
onView(withId(R.id.rv_devices)).check(ViewAssertions.matches(ViewMatchers.isCompletelyDisplayed()))
onView(withId(R.id.rv_devices)).check(matches(hasDescendant(withText("name1Item"))))
onView(withId(R.id.rv_devices)).check(matches(hasDescendant(withText("name2"))))
onView(withId(R.id.rv_devices)).check(matches(hasDescendant(withText("name3"))))
}
@TestfundevicesFailure(){
devicesFailure.postValue("error")
onView(withId(R.id.rv_devices)).check(ViewAssertions.matches(ViewMatchers.isCompletelyDisplayed()))
Mockito.verify(router, times(1)).loginActivity(testFragment.activity!!)
}
@TestfundevicesCall() {
onView(withId(R.id.rv_devices)).check(ViewAssertions.matches(ViewMatchers.isCompletelyDisplayed()))
Mockito.verify(dashboardViewModel, Mockito.times(1)).getDevices()
}
}
Solution 4:
In the example you provided, you are using mockito to return a mock for a specific instance of your view model, and not for every instance.
In order to make this work, you will have to have your fragment use the exact view model mock that you have created.
Most likely this would come from a store or a repository, so you could put your mock there? It really depends on how you setup the acquisition of the view model in your Fragments logic.
Recommendations: 1) Mock the data sources the view model is constructed from or 2) add a fragment.setViewModel() and Mark it as only for use in tests. This is a little ugly, but if you don't want to mock data sources, it is pretty easy this way.
Solution 5:
One could easily mock a ViewModel and other objects without Dagger simply by:
Create a wrapper class that can re-route calls to the ViewModelProvider. Below is the production version of the wrapper class that simply passes the calls to the real ViewModelProvider which is passed in as a parameter.
classVMProviderInterceptorImpl : VMProviderInterceptor { override fun get(viewModelProvider: ViewModelProvider, x: Class<out ViewModel>): ViewModel { return viewModelProvider.get(x) }
}
Adding getters and setters for this wrapper object to the Application class.
In the Activity rule, before an activity is launched, swap out the real wrapper with a mocked wrapper that does not route the get ViewModel call to the real viewModelProvider and instead provides a mocked object.
I realize this is not as powerful as dagger but the simplicity is attractive.
Post a Comment for "How To Correctly Mock Viewmodel On Androidtest"